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