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.inputmethodservice.InputMethodService;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.text.Layout;
27 import android.text.SpannedString;
28 import android.text.TextUtils;
29 import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
30 import android.widget.TextView;
31 
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Objects;
37 
38 /**
39  * Positional information about the text insertion point and characters in the composition string.
40  *
41  * <p>This class encapsulates locations of the text insertion point and the composition string in
42  * the screen coordinates so that IMEs can render their UI components near where the text is
43  * actually inserted.</p>
44  */
45 public final class CursorAnchorInfo implements Parcelable {
46     /**
47      * The pre-computed hash code.
48      */
49     private final int mHashCode;
50 
51     /**
52      * The index of the first character of the selected text (inclusive). {@code -1} when there is
53      * no text selection.
54      */
55     private final int mSelectionStart;
56     /**
57      * The index of the first character of the selected text (exclusive). {@code -1} when there is
58      * no text selection.
59      */
60     private final int mSelectionEnd;
61 
62     /**
63      * The index of the first character of the composing text (inclusive). {@code -1} when there is
64      * no composing text.
65      */
66     private final int mComposingTextStart;
67     /**
68      * The text, tracked as a composing region.
69      */
70     private final CharSequence mComposingText;
71 
72     /**
73      * Flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for example.
74      */
75     private final int mInsertionMarkerFlags;
76     /**
77      * Horizontal position of the insertion marker, in the local coordinates that will be
78      * transformed with the transformation matrix when rendered on the screen. This should be
79      * calculated or compatible with {@link Layout#getPrimaryHorizontal(int)}. This can be
80      * {@code java.lang.Float.NaN} when no value is specified.
81      */
82     private final float mInsertionMarkerHorizontal;
83     /**
84      * Vertical position of the insertion marker, in the local coordinates that will be
85      * transformed with the transformation matrix when rendered on the screen. This should be
86      * calculated or compatible with {@link Layout#getLineTop(int)}. This can be
87      * {@code java.lang.Float.NaN} when no value is specified.
88      */
89     private final float mInsertionMarkerTop;
90     /**
91      * Vertical position of the insertion marker, in the local coordinates that will be
92      * transformed with the transformation matrix when rendered on the screen. This should be
93      * calculated or compatible with {@link Layout#getLineBaseline(int)}. This can be
94      * {@code java.lang.Float.NaN} when no value is specified.
95      */
96     private final float mInsertionMarkerBaseline;
97     /**
98      * Vertical position of the insertion marker, in the local coordinates that will be
99      * transformed with the transformation matrix when rendered on the screen. This should be
100      * calculated or compatible with {@link Layout#getLineBottom(int)}. This can be
101      * {@code java.lang.Float.NaN} when no value is specified.
102      */
103     private final float mInsertionMarkerBottom;
104 
105     /**
106      * Container of rectangular position of characters, keyed with character index in a unit of
107      * Java chars, in the local coordinates that will be transformed with the transformation matrix
108      * when rendered on the screen.
109      */
110     private final SparseRectFArray mCharacterBoundsArray;
111 
112     /**
113      * Container of rectangular position of Editor in the local coordinates that will be transformed
114      * with the transformation matrix when rendered on the screen.
115      * @see EditorBoundsInfo
116      */
117     private final EditorBoundsInfo mEditorBoundsInfo;
118 
119     /**
120      * Transformation matrix that is applied to any positional information of this class to
121      * transform local coordinates into screen coordinates.
122      */
123     @NonNull
124     private final float[] mMatrixValues;
125 
126     /**
127      * Information about text appearance in the editor for use by {@link InputMethodService}.
128      */
129     @Nullable
130     private final TextAppearanceInfo mTextAppearanceInfo;
131 
132     /**
133      * A list of visible line bounds stored in a float array. This array is divided into segment of
134      * four where each element in the segment represents left, top, right respectively and bottom
135      * of the line bounds.
136      */
137     private final float[] mVisibleLineBounds;
138 
139     /**
140      * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
141      * insertion marker or character bounds have at least one visible region.
142      */
143     public static final int FLAG_HAS_VISIBLE_REGION = 0x01;
144 
145     /**
146      * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
147      * insertion marker or character bounds have at least one invisible (clipped) region.
148      */
149     public static final int FLAG_HAS_INVISIBLE_REGION = 0x02;
150 
151     /**
152      * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the
153      * insertion marker or character bounds is placed at right-to-left (RTL) character.
154      */
155     public static final int FLAG_IS_RTL = 0x04;
156 
CursorAnchorInfo(final Parcel source)157     public CursorAnchorInfo(final Parcel source) {
158         mHashCode = source.readInt();
159         mSelectionStart = source.readInt();
160         mSelectionEnd = source.readInt();
161         mComposingTextStart = source.readInt();
162         mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
163         mInsertionMarkerFlags = source.readInt();
164         mInsertionMarkerHorizontal = source.readFloat();
165         mInsertionMarkerTop = source.readFloat();
166         mInsertionMarkerBaseline = source.readFloat();
167         mInsertionMarkerBottom = source.readFloat();
168         mCharacterBoundsArray = source.readTypedObject(SparseRectFArray.CREATOR);
169         mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR);
170         mMatrixValues = source.createFloatArray();
171         mVisibleLineBounds = source.createFloatArray();
172         mTextAppearanceInfo = source.readTypedObject(TextAppearanceInfo.CREATOR);
173     }
174 
175     /**
176      * Used to package this object into a {@link Parcel}.
177      *
178      * @param dest The {@link Parcel} to be written.
179      * @param flags The flags used for parceling.
180      */
181     @Override
writeToParcel(Parcel dest, int flags)182     public void writeToParcel(Parcel dest, int flags) {
183         dest.writeInt(mHashCode);
184         dest.writeInt(mSelectionStart);
185         dest.writeInt(mSelectionEnd);
186         dest.writeInt(mComposingTextStart);
187         TextUtils.writeToParcel(mComposingText, dest, flags);
188         dest.writeInt(mInsertionMarkerFlags);
189         dest.writeFloat(mInsertionMarkerHorizontal);
190         dest.writeFloat(mInsertionMarkerTop);
191         dest.writeFloat(mInsertionMarkerBaseline);
192         dest.writeFloat(mInsertionMarkerBottom);
193         dest.writeTypedObject(mCharacterBoundsArray, flags);
194         dest.writeTypedObject(mEditorBoundsInfo, flags);
195         dest.writeFloatArray(mMatrixValues);
196         dest.writeFloatArray(mVisibleLineBounds);
197         dest.writeTypedObject(mTextAppearanceInfo, flags);
198     }
199 
200     @Override
hashCode()201     public int hashCode(){
202         return mHashCode;
203     }
204 
205     /**
206      * Compares two float values. Returns {@code true} if {@code a} and {@code b} are
207      * {@link Float#NaN} at the same time.
208      */
areSameFloatImpl(final float a, final float b)209     private static boolean areSameFloatImpl(final float a, final float b) {
210         if (Float.isNaN(a) && Float.isNaN(b)) {
211             return true;
212         }
213         return a == b;
214     }
215 
216     @Override
equals(@ullable Object obj)217     public boolean equals(@Nullable Object obj){
218         if (obj == null) {
219             return false;
220         }
221         if (this == obj) {
222             return true;
223         }
224         if (!(obj instanceof CursorAnchorInfo)) {
225             return false;
226         }
227         final CursorAnchorInfo that = (CursorAnchorInfo) obj;
228         if (hashCode() != that.hashCode()) {
229             return false;
230         }
231 
232         // Check fields that are not covered by hashCode() first.
233 
234         if (mSelectionStart != that.mSelectionStart || mSelectionEnd != that.mSelectionEnd) {
235             return false;
236         }
237 
238         if (mInsertionMarkerFlags != that.mInsertionMarkerFlags
239                 || !areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal)
240                 || !areSameFloatImpl(mInsertionMarkerTop, that.mInsertionMarkerTop)
241                 || !areSameFloatImpl(mInsertionMarkerBaseline, that.mInsertionMarkerBaseline)
242                 || !areSameFloatImpl(mInsertionMarkerBottom, that.mInsertionMarkerBottom)) {
243             return false;
244         }
245 
246         if (!Objects.equals(mCharacterBoundsArray, that.mCharacterBoundsArray)) {
247             return false;
248         }
249 
250         if (!Objects.equals(mEditorBoundsInfo, that.mEditorBoundsInfo)) {
251             return false;
252         }
253 
254         if (!Arrays.equals(mVisibleLineBounds, that.mVisibleLineBounds)) {
255             return false;
256         }
257 
258         // Following fields are (partially) covered by hashCode().
259 
260         if (mComposingTextStart != that.mComposingTextStart
261                 || !Objects.equals(mComposingText, that.mComposingText)) {
262             return false;
263         }
264 
265         // We do not use Arrays.equals(float[], float[]) to keep the previous behavior regarding
266         // NaN, 0.0f, and -0.0f.
267         if (mMatrixValues.length != that.mMatrixValues.length) {
268             return false;
269         }
270         for (int i = 0; i < mMatrixValues.length; ++i) {
271             if (mMatrixValues[i] != that.mMatrixValues[i]) {
272                 return false;
273             }
274         }
275 
276         if (!Objects.equals(mTextAppearanceInfo, that.mTextAppearanceInfo)) {
277             return false;
278         }
279 
280         return true;
281     }
282 
283     @Override
toString()284     public String toString() {
285         return "CursorAnchorInfo{mHashCode=" + mHashCode
286                 + " mSelection=" + mSelectionStart + "," + mSelectionEnd
287                 + " mComposingTextStart=" + mComposingTextStart
288                 + " mComposingText=" + mComposingText
289                 + " mInsertionMarkerFlags=" + mInsertionMarkerFlags
290                 + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
291                 + " mInsertionMarkerTop=" + mInsertionMarkerTop
292                 + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
293                 + " mInsertionMarkerBottom=" + mInsertionMarkerBottom
294                 + " mCharacterBoundsArray=" + mCharacterBoundsArray
295                 + " mEditorBoundsInfo=" + mEditorBoundsInfo
296                 + " mVisibleLineBounds=" + getVisibleLineBounds()
297                 + " mMatrix=" + Arrays.toString(mMatrixValues)
298                 + " mTextAppearanceInfo=" + mTextAppearanceInfo
299                 + "}";
300     }
301 
302     /**
303      * Builder for {@link CursorAnchorInfo}. This class is not designed to be thread-safe.
304      */
305     public static final class Builder {
306         private static final int LINE_BOUNDS_INITIAL_SIZE = 4;
307         private int mSelectionStart = -1;
308         private int mSelectionEnd = -1;
309         private int mComposingTextStart = -1;
310         private CharSequence mComposingText = null;
311         private float mInsertionMarkerHorizontal = Float.NaN;
312         private float mInsertionMarkerTop = Float.NaN;
313         private float mInsertionMarkerBaseline = Float.NaN;
314         private float mInsertionMarkerBottom = Float.NaN;
315         private int mInsertionMarkerFlags = 0;
316         private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null;
317         private EditorBoundsInfo mEditorBoundsInfo = null;
318         private float[] mMatrixValues = null;
319         private boolean mMatrixInitialized = false;
320         private float[] mVisibleLineBounds = new float[LINE_BOUNDS_INITIAL_SIZE * 4];
321         private int mVisibleLineBoundsCount = 0;
322         private TextAppearanceInfo mTextAppearanceInfo = null;
323 
324         /**
325          * Sets the text range of the selection. Calling this can be skipped if there is no
326          * selection.
327          */
setSelectionRange(final int newStart, final int newEnd)328         public Builder setSelectionRange(final int newStart, final int newEnd) {
329             mSelectionStart = newStart;
330             mSelectionEnd = newEnd;
331             return this;
332         }
333 
334         /**
335          * Sets the text range of the composing text. Calling this can be skipped if there is
336          * no composing text.
337          * @param composingTextStart index where the composing text starts.
338          * @param composingText the entire composing text.
339          */
setComposingText(final int composingTextStart, final CharSequence composingText)340         public Builder setComposingText(final int composingTextStart,
341             final CharSequence composingText) {
342             mComposingTextStart = composingTextStart;
343             if (composingText == null) {
344                 mComposingText = null;
345             } else {
346                 // Make a snapshot of the given char sequence.
347                 mComposingText = new SpannedString(composingText);
348             }
349             return this;
350         }
351 
352         /**
353          * Sets the location of the text insertion point (zero width cursor) as a rectangle in
354          * local coordinates. Calling this can be skipped when there is no text insertion point;
355          * however if there is an insertion point, editors must call this method.
356          * @param horizontalPosition horizontal position of the insertion marker, in the local
357          * coordinates that will be transformed with the transformation matrix when rendered on the
358          * screen. This should be calculated or compatible with
359          * {@link Layout#getPrimaryHorizontal(int)}.
360          * @param lineTop vertical position of the insertion marker, in the local coordinates that
361          * will be transformed with the transformation matrix when rendered on the screen. This
362          * should be calculated or compatible with {@link Layout#getLineTop(int)}.
363          * @param lineBaseline vertical position of the insertion marker, in the local coordinates
364          * that will be transformed with the transformation matrix when rendered on the screen. This
365          * should be calculated or compatible with {@link Layout#getLineBaseline(int)}.
366          * @param lineBottom vertical position of the insertion marker, in the local coordinates
367          * that will be transformed with the transformation matrix when rendered on the screen. This
368          * should be calculated or compatible with {@link Layout#getLineBottom(int)}.
369          * @param flags flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for
370          * example.
371          */
setInsertionMarkerLocation(final float horizontalPosition, final float lineTop, final float lineBaseline, final float lineBottom, final int flags)372         public Builder setInsertionMarkerLocation(final float horizontalPosition,
373                 final float lineTop, final float lineBaseline, final float lineBottom,
374                 final int flags){
375             mInsertionMarkerHorizontal = horizontalPosition;
376             mInsertionMarkerTop = lineTop;
377             mInsertionMarkerBaseline = lineBaseline;
378             mInsertionMarkerBottom = lineBottom;
379             mInsertionMarkerFlags = flags;
380             return this;
381         }
382 
383         /**
384          * Adds the bounding box of the character specified with the index.
385          *
386          * @param index index of the character in Java chars units. Must be specified in
387          * ascending order across successive calls.
388          * @param left x coordinate of the left edge of the character in local coordinates.
389          * @param top y coordinate of the top edge of the character in local coordinates.
390          * @param right x coordinate of the right edge of the character in local coordinates.
391          * @param bottom y coordinate of the bottom edge of the character in local coordinates.
392          * @param flags flags for this character bounds. See {@link #FLAG_HAS_VISIBLE_REGION},
393          * {@link #FLAG_HAS_INVISIBLE_REGION} and {@link #FLAG_IS_RTL}. These flags must be
394          * specified when necessary.
395          * @throws IllegalArgumentException If the index is a negative value, or not greater than
396          * all of the previously called indices.
397          */
addCharacterBounds(final int index, final float left, final float top, final float right, final float bottom, final int flags)398         public Builder addCharacterBounds(final int index, final float left, final float top,
399                 final float right, final float bottom, final int flags) {
400             if (index < 0) {
401                 throw new IllegalArgumentException("index must not be a negative integer.");
402             }
403             if (mCharacterBoundsArrayBuilder == null) {
404                 mCharacterBoundsArrayBuilder = new SparseRectFArrayBuilder();
405             }
406             mCharacterBoundsArrayBuilder.append(index, left, top, right, bottom, flags);
407             return this;
408         }
409 
410         /**
411          * Sets the current editor related bounds.
412          *
413          * @param bounds {@link EditorBoundsInfo} in local coordinates.
414          */
415         @NonNull
setEditorBoundsInfo(@ullable EditorBoundsInfo bounds)416         public Builder setEditorBoundsInfo(@Nullable EditorBoundsInfo bounds) {
417             mEditorBoundsInfo = bounds;
418             return this;
419         }
420 
421         /**
422          * Sets the matrix that transforms local coordinates into screen coordinates.
423          * @param matrix transformation matrix from local coordinates into screen coordinates. null
424          * is interpreted as an identity matrix.
425          */
setMatrix(final Matrix matrix)426         public Builder setMatrix(final Matrix matrix) {
427             if (mMatrixValues == null) {
428                 mMatrixValues = new float[9];
429             }
430             (matrix != null ? matrix : Matrix.IDENTITY_MATRIX).getValues(mMatrixValues);
431             mMatrixInitialized = true;
432             return this;
433         }
434 
435         /**
436          * Set the information related to text appearance, which is extracted from the original
437          * {@link TextView}.
438          * @param textAppearanceInfo {@link TextAppearanceInfo} of TextView.
439          */
440         @NonNull
setTextAppearanceInfo(@ullable TextAppearanceInfo textAppearanceInfo)441         public Builder setTextAppearanceInfo(@Nullable TextAppearanceInfo textAppearanceInfo) {
442             mTextAppearanceInfo = textAppearanceInfo;
443             return this;
444         }
445 
446         /**
447          * Add the bounds of a visible text line of the current editor.
448          *
449          * The line bounds should not include the vertical space between lines or the horizontal
450          * space before and after a line.
451          * It's preferable if the line bounds are added in the logical order, so that IME can
452          * process them easily.
453          *
454          * @param left the left bound of the left-most character in the line
455          * @param top the top bound of the top-most character in the line
456          * @param right the right bound of the right-most character in the line
457          * @param bottom the bottom bound of the bottom-most character in the line
458          *
459          * @see CursorAnchorInfo#getVisibleLineBounds()
460          * @see #clearVisibleLineBounds()
461          */
462         @NonNull
addVisibleLineBounds(float left, float top, float right, float bottom)463         public Builder addVisibleLineBounds(float left, float top, float right, float bottom) {
464             if (mVisibleLineBounds.length <= mVisibleLineBoundsCount + 4) {
465                 mVisibleLineBounds =
466                         Arrays.copyOf(mVisibleLineBounds, (mVisibleLineBoundsCount + 4) * 2);
467             }
468             mVisibleLineBounds[mVisibleLineBoundsCount++] = left;
469             mVisibleLineBounds[mVisibleLineBoundsCount++] = top;
470             mVisibleLineBounds[mVisibleLineBoundsCount++] = right;
471             mVisibleLineBounds[mVisibleLineBoundsCount++] = bottom;
472             return this;
473         }
474 
475         /**
476          * Clear the visible text line bounds previously added to this {@link Builder}.
477          *
478          * @see #addVisibleLineBounds(float, float, float, float)
479          */
480         @NonNull
clearVisibleLineBounds()481         public Builder clearVisibleLineBounds() {
482             // Since mVisibleLineBounds is copied in build(), we only need to reset
483             // mVisibleLineBoundsCount to 0. And mVisibleLineBounds will be reused for better
484             // performance.
485             mVisibleLineBoundsCount = 0;
486             return this;
487         }
488 
489         /**
490          * @return {@link CursorAnchorInfo} using parameters in this {@link Builder}.
491          * @throws IllegalArgumentException if one or more positional parameters are specified but
492          * the coordinate transformation matrix is not provided via {@link #setMatrix(Matrix)}.
493          */
build()494         public CursorAnchorInfo build() {
495             if (!mMatrixInitialized) {
496                 // Coordinate transformation matrix is mandatory when at least one positional
497                 // parameter is specified.
498                 final boolean hasCharacterBounds = (mCharacterBoundsArrayBuilder != null
499                         && !mCharacterBoundsArrayBuilder.isEmpty());
500                 final boolean hasVisibleLineBounds = (mVisibleLineBounds != null
501                         && mVisibleLineBoundsCount > 0);
502                 if (hasCharacterBounds
503                         || hasVisibleLineBounds
504                         || !Float.isNaN(mInsertionMarkerHorizontal)
505                         || !Float.isNaN(mInsertionMarkerTop)
506                         || !Float.isNaN(mInsertionMarkerBaseline)
507                         || !Float.isNaN(mInsertionMarkerBottom)) {
508                     throw new IllegalArgumentException("Coordinate transformation matrix is " +
509                             "required when positional parameters are specified.");
510                 }
511             }
512             return CursorAnchorInfo.create(this);
513         }
514 
515         /**
516          * Resets the internal state so that this instance can be reused to build another
517          * instance of {@link CursorAnchorInfo}.
518          */
reset()519         public void reset() {
520             mSelectionStart = -1;
521             mSelectionEnd = -1;
522             mComposingTextStart = -1;
523             mComposingText = null;
524             mInsertionMarkerFlags = 0;
525             mInsertionMarkerHorizontal = Float.NaN;
526             mInsertionMarkerTop = Float.NaN;
527             mInsertionMarkerBaseline = Float.NaN;
528             mInsertionMarkerBottom = Float.NaN;
529             mMatrixInitialized = false;
530             if (mCharacterBoundsArrayBuilder != null) {
531                 mCharacterBoundsArrayBuilder.reset();
532             }
533             mEditorBoundsInfo = null;
534             clearVisibleLineBounds();
535             mTextAppearanceInfo = null;
536         }
537     }
538 
create(Builder builder)539     private static CursorAnchorInfo create(Builder builder) {
540         final SparseRectFArray characterBoundsArray =
541                 builder.mCharacterBoundsArrayBuilder != null
542                         ? builder.mCharacterBoundsArrayBuilder.build()
543                         : null;
544         final float[] matrixValues = new float[9];
545         if (builder.mMatrixInitialized) {
546             System.arraycopy(builder.mMatrixValues, 0, matrixValues, 0, 9);
547         } else {
548             Matrix.IDENTITY_MATRIX.getValues(matrixValues);
549         }
550 
551         return new CursorAnchorInfo(builder.mSelectionStart, builder.mSelectionEnd,
552                 builder.mComposingTextStart, builder.mComposingText, builder.mInsertionMarkerFlags,
553                 builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop,
554                 builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom,
555                 characterBoundsArray, builder.mEditorBoundsInfo, matrixValues,
556                 Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount),
557                 builder.mTextAppearanceInfo);
558     }
559 
CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart, @Nullable CharSequence composingText, int insertionMarkerFlags, float insertionMarkerHorizontal, float insertionMarkerTop, float insertionMarkerBaseline, float insertionMarkerBottom, @Nullable SparseRectFArray characterBoundsArray, @Nullable EditorBoundsInfo editorBoundsInfo, @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds, @Nullable TextAppearanceInfo textAppearanceInfo)560     private CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart,
561             @Nullable CharSequence composingText, int insertionMarkerFlags,
562             float insertionMarkerHorizontal, float insertionMarkerTop,
563             float insertionMarkerBaseline, float insertionMarkerBottom,
564             @Nullable SparseRectFArray characterBoundsArray,
565             @Nullable EditorBoundsInfo editorBoundsInfo,
566             @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds,
567             @Nullable TextAppearanceInfo textAppearanceInfo) {
568         mSelectionStart = selectionStart;
569         mSelectionEnd = selectionEnd;
570         mComposingTextStart = composingTextStart;
571         mComposingText = composingText;
572         mInsertionMarkerFlags = insertionMarkerFlags;
573         mInsertionMarkerHorizontal = insertionMarkerHorizontal;
574         mInsertionMarkerTop = insertionMarkerTop;
575         mInsertionMarkerBaseline = insertionMarkerBaseline;
576         mInsertionMarkerBottom = insertionMarkerBottom;
577         mCharacterBoundsArray = characterBoundsArray;
578         mEditorBoundsInfo = editorBoundsInfo;
579         mMatrixValues = matrixValues;
580         mVisibleLineBounds = visibleLineBounds;
581         mTextAppearanceInfo = textAppearanceInfo;
582 
583         // To keep hash function simple, we only use some complex objects for hash.
584         int hashCode = Objects.hashCode(mComposingText);
585         hashCode *= 31;
586         hashCode += Arrays.hashCode(mMatrixValues);
587         mHashCode = hashCode;
588     }
589 
590     /**
591      * Creates a new instance of {@link CursorAnchorInfo} by applying {@code parentMatrix} to
592      * the coordinate transformation matrix.
593      *
594      * @param original     {@link CursorAnchorInfo} to be cloned from.
595      * @param parentMatrix {@link Matrix} to be applied to {@code original.getMatrix()}
596      * @return A new instance of {@link CursorAnchorInfo} whose {@link CursorAnchorInfo#getMatrix()}
597      *         returns {@code parentMatrix * original.getMatrix()}.
598      * @hide
599      */
createForAdditionalParentMatrix(CursorAnchorInfo original, @NonNull Matrix parentMatrix)600     public static CursorAnchorInfo createForAdditionalParentMatrix(CursorAnchorInfo original,
601             @NonNull Matrix parentMatrix) {
602         return new CursorAnchorInfo(original.mSelectionStart, original.mSelectionEnd,
603                 original.mComposingTextStart, original.mComposingText,
604                 original.mInsertionMarkerFlags, original.mInsertionMarkerHorizontal,
605                 original.mInsertionMarkerTop, original.mInsertionMarkerBaseline,
606                 original.mInsertionMarkerBottom, original.mCharacterBoundsArray,
607                 original.mEditorBoundsInfo, computeMatrixValues(parentMatrix, original),
608                 original.mVisibleLineBounds, original.mTextAppearanceInfo);
609     }
610 
611     /**
612      * Returns a float array that represents {@link Matrix} elements for
613      * {@code parentMatrix * info.getMatrix()}.
614      *
615      * @param parentMatrix {@link Matrix} to be multiplied.
616      * @param info         {@link CursorAnchorInfo} to provide {@link Matrix} to be multiplied.
617      * @return {@code parentMatrix * info.getMatrix()}.
618      */
computeMatrixValues(@onNull Matrix parentMatrix, @NonNull CursorAnchorInfo info)619     private static float[] computeMatrixValues(@NonNull Matrix parentMatrix,
620             @NonNull CursorAnchorInfo info) {
621         if (parentMatrix.isIdentity()) {
622             return info.mMatrixValues;
623         }
624 
625         final Matrix newMatrix = new Matrix();
626         newMatrix.setValues(info.mMatrixValues);
627         newMatrix.postConcat(parentMatrix);
628 
629         final float[] matrixValues = new float[9];
630         newMatrix.getValues(matrixValues);
631         return matrixValues;
632     }
633 
634     /**
635      * Returns the index where the selection starts.
636      * @return {@code -1} if there is no selection.
637      */
getSelectionStart()638     public int getSelectionStart() {
639         return mSelectionStart;
640     }
641 
642     /**
643      * Returns the index where the selection ends.
644      * @return {@code -1} if there is no selection.
645      */
getSelectionEnd()646     public int getSelectionEnd() {
647         return mSelectionEnd;
648     }
649 
650     /**
651      * Returns the index where the composing text starts.
652      * @return {@code -1} if there is no composing text.
653      */
getComposingTextStart()654     public int getComposingTextStart() {
655         return mComposingTextStart;
656     }
657 
658     /**
659      * Returns the entire composing text.
660      * @return {@code null} if there is no composition.
661      */
getComposingText()662     public CharSequence getComposingText() {
663         return mComposingText;
664     }
665 
666     /**
667      * Returns the flag of the insertion marker.
668      * @return the flag of the insertion marker. {@code 0} if no flag is specified.
669      */
getInsertionMarkerFlags()670     public int getInsertionMarkerFlags() {
671         return mInsertionMarkerFlags;
672     }
673 
674     /**
675      * Returns the horizontal start of the insertion marker, in the local coordinates that will
676      * be transformed with {@link #getMatrix()} when rendered on the screen.
677      * @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}.
678      * Pay special care to RTL/LTR handling.
679      * {@code java.lang.Float.NaN} if not specified.
680      * @see Layout#getPrimaryHorizontal(int)
681      */
getInsertionMarkerHorizontal()682     public float getInsertionMarkerHorizontal() {
683         return mInsertionMarkerHorizontal;
684     }
685 
686     /**
687      * Returns the vertical top position of the insertion marker, in the local coordinates that
688      * will be transformed with {@link #getMatrix()} when rendered on the screen.
689      * @return y coordinate that is compatible with {@link Layout#getLineTop(int)}.
690      * {@code java.lang.Float.NaN} if not specified.
691      */
getInsertionMarkerTop()692     public float getInsertionMarkerTop() {
693         return mInsertionMarkerTop;
694     }
695 
696     /**
697      * Returns the vertical baseline position of the insertion marker, in the local coordinates
698      * that will be transformed with {@link #getMatrix()} when rendered on the screen.
699      * @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}.
700      * {@code java.lang.Float.NaN} if not specified.
701      */
getInsertionMarkerBaseline()702     public float getInsertionMarkerBaseline() {
703         return mInsertionMarkerBaseline;
704     }
705 
706     /**
707      * Returns the vertical bottom position of the insertion marker, in the local coordinates
708      * that will be transformed with {@link #getMatrix()} when rendered on the screen.
709      * @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}.
710      * {@code java.lang.Float.NaN} if not specified.
711      */
getInsertionMarkerBottom()712     public float getInsertionMarkerBottom() {
713         return mInsertionMarkerBottom;
714     }
715 
716     /**
717      * Returns a new instance of {@link RectF} that indicates the location of the character
718      * specified with the index.
719      * @param index index of the character in a Java chars.
720      * @return the character bounds in local coordinates as a new instance of {@link RectF}.
721      */
getCharacterBounds(final int index)722     public RectF getCharacterBounds(final int index) {
723         if (mCharacterBoundsArray == null) {
724             return null;
725         }
726         return mCharacterBoundsArray.get(index);
727     }
728 
729     /**
730      * Returns the flags associated with the character bounds specified with the index.
731      * @param index index of the character in a Java chars.
732      * @return {@code 0} if no flag is specified.
733      */
getCharacterBoundsFlags(final int index)734     public int getCharacterBoundsFlags(final int index) {
735         if (mCharacterBoundsArray == null) {
736             return 0;
737         }
738         return mCharacterBoundsArray.getFlags(index, 0);
739     }
740 
741     /**
742      * Returns the list of {@link RectF}s indicating the locations of the visible line bounds in
743      * the editor.
744      * @return the visible line bounds in the local coordinates as a list of {@link RectF}.
745      *
746      * @see Builder#addVisibleLineBounds(float, float, float, float)
747      */
748     @NonNull
getVisibleLineBounds()749     public List<RectF> getVisibleLineBounds() {
750         if (mVisibleLineBounds == null) {
751             return Collections.emptyList();
752         }
753         final List<RectF> result = new ArrayList<>(mVisibleLineBounds.length / 4);
754         for (int index = 0; index < mVisibleLineBounds.length;) {
755             final RectF rectF = new RectF(
756                     mVisibleLineBounds[index++],
757                     mVisibleLineBounds[index++],
758                     mVisibleLineBounds[index++],
759                     mVisibleLineBounds[index++]);
760             result.add(rectF);
761         }
762         return result;
763     }
764 
765     /**
766      * Returns {@link EditorBoundsInfo} for the current editor, or {@code null} if IME is not
767      * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}
768      * or {@link InputConnection#CURSOR_UPDATE_MONITOR}.
769      */
770     @Nullable
getEditorBoundsInfo()771     public EditorBoundsInfo getEditorBoundsInfo() {
772         return mEditorBoundsInfo;
773     }
774 
775     /**
776      * Returns {@link TextAppearanceInfo} for the current editor, or {@code null} if IME is not
777      * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}
778      * or {@link InputConnection#CURSOR_UPDATE_MONITOR}.
779      */
780     @Nullable
getTextAppearanceInfo()781     public TextAppearanceInfo getTextAppearanceInfo() {
782         return mTextAppearanceInfo;
783     }
784 
785     /**
786      * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
787      * matrix that is to be applied other positional data in this class.
788      * @return a new instance (copy) of the transformation matrix.
789      */
getMatrix()790     public Matrix getMatrix() {
791         final Matrix matrix = new Matrix();
792         matrix.setValues(mMatrixValues);
793         return matrix;
794     }
795 
796     /**
797      * Used to make this class parcelable.
798      */
799     public static final @android.annotation.NonNull Parcelable.Creator<CursorAnchorInfo> CREATOR
800             = new Parcelable.Creator<CursorAnchorInfo>() {
801         @Override
802         public CursorAnchorInfo createFromParcel(Parcel source) {
803             return new CursorAnchorInfo(source);
804         }
805 
806         @Override
807         public CursorAnchorInfo[] newArray(int size) {
808             return new CursorAnchorInfo[size];
809         }
810     };
811 
812     @Override
describeContents()813     public int describeContents() {
814         return 0;
815     }
816 }
817