• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(@ullable Object obj)186     public boolean equals(@Nullable 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 new CursorAnchorInfo(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 
CursorAnchorInfo(final Builder builder)416     private CursorAnchorInfo(final Builder builder) {
417         mSelectionStart = builder.mSelectionStart;
418         mSelectionEnd = builder.mSelectionEnd;
419         mComposingTextStart = builder.mComposingTextStart;
420         mComposingText = builder.mComposingText;
421         mInsertionMarkerFlags = builder.mInsertionMarkerFlags;
422         mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal;
423         mInsertionMarkerTop = builder.mInsertionMarkerTop;
424         mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline;
425         mInsertionMarkerBottom = builder.mInsertionMarkerBottom;
426         mCharacterBoundsArray = builder.mCharacterBoundsArrayBuilder != null
427                 ? builder.mCharacterBoundsArrayBuilder.build() : null;
428         mMatrixValues = new float[9];
429         if (builder.mMatrixInitialized) {
430             System.arraycopy(builder.mMatrixValues, 0, mMatrixValues, 0, 9);
431         } else {
432             Matrix.IDENTITY_MATRIX.getValues(mMatrixValues);
433         }
434 
435         // To keep hash function simple, we only use some complex objects for hash.
436         int hash = Objects.hashCode(mComposingText);
437         hash *= 31;
438         hash += Arrays.hashCode(mMatrixValues);
439         mHashCode = hash;
440     }
441 
442     /**
443      * Returns the index where the selection starts.
444      * @return {@code -1} if there is no selection.
445      */
getSelectionStart()446     public int getSelectionStart() {
447         return mSelectionStart;
448     }
449 
450     /**
451      * Returns the index where the selection ends.
452      * @return {@code -1} if there is no selection.
453      */
getSelectionEnd()454     public int getSelectionEnd() {
455         return mSelectionEnd;
456     }
457 
458     /**
459      * Returns the index where the composing text starts.
460      * @return {@code -1} if there is no composing text.
461      */
getComposingTextStart()462     public int getComposingTextStart() {
463         return mComposingTextStart;
464     }
465 
466     /**
467      * Returns the entire composing text.
468      * @return {@code null} if there is no composition.
469      */
getComposingText()470     public CharSequence getComposingText() {
471         return mComposingText;
472     }
473 
474     /**
475      * Returns the flag of the insertion marker.
476      * @return the flag of the insertion marker. {@code 0} if no flag is specified.
477      */
getInsertionMarkerFlags()478     public int getInsertionMarkerFlags() {
479         return mInsertionMarkerFlags;
480     }
481 
482     /**
483      * Returns the horizontal start of the insertion marker, in the local coordinates that will
484      * be transformed with {@link #getMatrix()} when rendered on the screen.
485      * @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}.
486      * Pay special care to RTL/LTR handling.
487      * {@code java.lang.Float.NaN} if not specified.
488      * @see Layout#getPrimaryHorizontal(int)
489      */
getInsertionMarkerHorizontal()490     public float getInsertionMarkerHorizontal() {
491         return mInsertionMarkerHorizontal;
492     }
493 
494     /**
495      * Returns the vertical top position of the insertion marker, in the local coordinates that
496      * will be transformed with {@link #getMatrix()} when rendered on the screen.
497      * @return y coordinate that is compatible with {@link Layout#getLineTop(int)}.
498      * {@code java.lang.Float.NaN} if not specified.
499      */
getInsertionMarkerTop()500     public float getInsertionMarkerTop() {
501         return mInsertionMarkerTop;
502     }
503 
504     /**
505      * Returns the vertical baseline position of the insertion marker, in the local coordinates
506      * that will be transformed with {@link #getMatrix()} when rendered on the screen.
507      * @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}.
508      * {@code java.lang.Float.NaN} if not specified.
509      */
getInsertionMarkerBaseline()510     public float getInsertionMarkerBaseline() {
511         return mInsertionMarkerBaseline;
512     }
513 
514     /**
515      * Returns the vertical bottom position of the insertion marker, in the local coordinates
516      * that will be transformed with {@link #getMatrix()} when rendered on the screen.
517      * @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}.
518      * {@code java.lang.Float.NaN} if not specified.
519      */
getInsertionMarkerBottom()520     public float getInsertionMarkerBottom() {
521         return mInsertionMarkerBottom;
522     }
523 
524     /**
525      * Returns a new instance of {@link RectF} that indicates the location of the character
526      * specified with the index.
527      * @param index index of the character in a Java chars.
528      * @return the character bounds in local coordinates as a new instance of {@link RectF}.
529      */
getCharacterBounds(final int index)530     public RectF getCharacterBounds(final int index) {
531         if (mCharacterBoundsArray == null) {
532             return null;
533         }
534         return mCharacterBoundsArray.get(index);
535     }
536 
537     /**
538      * Returns the flags associated with the character bounds specified with the index.
539      * @param index index of the character in a Java chars.
540      * @return {@code 0} if no flag is specified.
541      */
getCharacterBoundsFlags(final int index)542     public int getCharacterBoundsFlags(final int index) {
543         if (mCharacterBoundsArray == null) {
544             return 0;
545         }
546         return mCharacterBoundsArray.getFlags(index, 0);
547     }
548 
549     /**
550      * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
551      * matrix that is to be applied other positional data in this class.
552      * @return a new instance (copy) of the transformation matrix.
553      */
getMatrix()554     public Matrix getMatrix() {
555         final Matrix matrix = new Matrix();
556         matrix.setValues(mMatrixValues);
557         return matrix;
558     }
559 
560     /**
561      * Used to make this class parcelable.
562      */
563     public static final @android.annotation.NonNull Parcelable.Creator<CursorAnchorInfo> CREATOR
564             = new Parcelable.Creator<CursorAnchorInfo>() {
565         @Override
566         public CursorAnchorInfo createFromParcel(Parcel source) {
567             return new CursorAnchorInfo(source);
568         }
569 
570         @Override
571         public CursorAnchorInfo[] newArray(int size) {
572             return new CursorAnchorInfo[size];
573         }
574     };
575 
576     @Override
describeContents()577     public int describeContents() {
578         return 0;
579     }
580 }
581