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 * Container of rectangular position of Editor in the local coordinates that will be transformed 109 * with the transformation matrix when rendered on the screen. 110 * @see {@link EditorBoundsInfo}. 111 */ 112 private final EditorBoundsInfo mEditorBoundsInfo; 113 114 /** 115 * Transformation matrix that is applied to any positional information of this class to 116 * transform local coordinates into screen coordinates. 117 */ 118 @NonNull 119 private final float[] mMatrixValues; 120 121 /** 122 * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the 123 * insertion marker or character bounds have at least one visible region. 124 */ 125 public static final int FLAG_HAS_VISIBLE_REGION = 0x01; 126 127 /** 128 * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the 129 * insertion marker or character bounds have at least one invisible (clipped) region. 130 */ 131 public static final int FLAG_HAS_INVISIBLE_REGION = 0x02; 132 133 /** 134 * Flag for {@link #getInsertionMarkerFlags()} and {@link #getCharacterBoundsFlags(int)}: the 135 * insertion marker or character bounds is placed at right-to-left (RTL) character. 136 */ 137 public static final int FLAG_IS_RTL = 0x04; 138 CursorAnchorInfo(final Parcel source)139 public CursorAnchorInfo(final Parcel source) { 140 mHashCode = source.readInt(); 141 mSelectionStart = source.readInt(); 142 mSelectionEnd = source.readInt(); 143 mComposingTextStart = source.readInt(); 144 mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); 145 mInsertionMarkerFlags = source.readInt(); 146 mInsertionMarkerHorizontal = source.readFloat(); 147 mInsertionMarkerTop = source.readFloat(); 148 mInsertionMarkerBaseline = source.readFloat(); 149 mInsertionMarkerBottom = source.readFloat(); 150 mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class); 151 mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR); 152 mMatrixValues = source.createFloatArray(); 153 } 154 155 /** 156 * Used to package this object into a {@link Parcel}. 157 * 158 * @param dest The {@link Parcel} to be written. 159 * @param flags The flags used for parceling. 160 */ 161 @Override writeToParcel(Parcel dest, int flags)162 public void writeToParcel(Parcel dest, int flags) { 163 dest.writeInt(mHashCode); 164 dest.writeInt(mSelectionStart); 165 dest.writeInt(mSelectionEnd); 166 dest.writeInt(mComposingTextStart); 167 TextUtils.writeToParcel(mComposingText, dest, flags); 168 dest.writeInt(mInsertionMarkerFlags); 169 dest.writeFloat(mInsertionMarkerHorizontal); 170 dest.writeFloat(mInsertionMarkerTop); 171 dest.writeFloat(mInsertionMarkerBaseline); 172 dest.writeFloat(mInsertionMarkerBottom); 173 dest.writeParcelable(mCharacterBoundsArray, flags); 174 dest.writeTypedObject(mEditorBoundsInfo, flags); 175 dest.writeFloatArray(mMatrixValues); 176 } 177 178 @Override hashCode()179 public int hashCode(){ 180 return mHashCode; 181 } 182 183 /** 184 * Compares two float values. Returns {@code true} if {@code a} and {@code b} are 185 * {@link Float#NaN} at the same time. 186 */ areSameFloatImpl(final float a, final float b)187 private static boolean areSameFloatImpl(final float a, final float b) { 188 if (Float.isNaN(a) && Float.isNaN(b)) { 189 return true; 190 } 191 return a == b; 192 } 193 194 @Override equals(@ullable Object obj)195 public boolean equals(@Nullable Object obj){ 196 if (obj == null) { 197 return false; 198 } 199 if (this == obj) { 200 return true; 201 } 202 if (!(obj instanceof CursorAnchorInfo)) { 203 return false; 204 } 205 final CursorAnchorInfo that = (CursorAnchorInfo) obj; 206 if (hashCode() != that.hashCode()) { 207 return false; 208 } 209 210 // Check fields that are not covered by hashCode() first. 211 212 if (mSelectionStart != that.mSelectionStart || mSelectionEnd != that.mSelectionEnd) { 213 return false; 214 } 215 216 if (mInsertionMarkerFlags != that.mInsertionMarkerFlags 217 || !areSameFloatImpl(mInsertionMarkerHorizontal, that.mInsertionMarkerHorizontal) 218 || !areSameFloatImpl(mInsertionMarkerTop, that.mInsertionMarkerTop) 219 || !areSameFloatImpl(mInsertionMarkerBaseline, that.mInsertionMarkerBaseline) 220 || !areSameFloatImpl(mInsertionMarkerBottom, that.mInsertionMarkerBottom)) { 221 return false; 222 } 223 224 if (!Objects.equals(mCharacterBoundsArray, that.mCharacterBoundsArray)) { 225 return false; 226 } 227 228 if (!Objects.equals(mEditorBoundsInfo, that.mEditorBoundsInfo)) { 229 return false; 230 } 231 232 // Following fields are (partially) covered by hashCode(). 233 234 if (mComposingTextStart != that.mComposingTextStart 235 || !Objects.equals(mComposingText, that.mComposingText)) { 236 return false; 237 } 238 239 // We do not use Arrays.equals(float[], float[]) to keep the previous behavior regarding 240 // NaN, 0.0f, and -0.0f. 241 if (mMatrixValues.length != that.mMatrixValues.length) { 242 return false; 243 } 244 for (int i = 0; i < mMatrixValues.length; ++i) { 245 if (mMatrixValues[i] != that.mMatrixValues[i]) { 246 return false; 247 } 248 } 249 return true; 250 } 251 252 @Override toString()253 public String toString() { 254 return "CursorAnchorInfo{mHashCode=" + mHashCode 255 + " mSelection=" + mSelectionStart + "," + mSelectionEnd 256 + " mComposingTextStart=" + mComposingTextStart 257 + " mComposingText=" + Objects.toString(mComposingText) 258 + " mInsertionMarkerFlags=" + mInsertionMarkerFlags 259 + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal 260 + " mInsertionMarkerTop=" + mInsertionMarkerTop 261 + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline 262 + " mInsertionMarkerBottom=" + mInsertionMarkerBottom 263 + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray) 264 + " mEditorBoundsInfo=" + mEditorBoundsInfo 265 + " mMatrix=" + Arrays.toString(mMatrixValues) 266 + "}"; 267 } 268 269 /** 270 * Builder for {@link CursorAnchorInfo}. This class is not designed to be thread-safe. 271 */ 272 public static final class Builder { 273 private int mSelectionStart = -1; 274 private int mSelectionEnd = -1; 275 private int mComposingTextStart = -1; 276 private CharSequence mComposingText = null; 277 private float mInsertionMarkerHorizontal = Float.NaN; 278 private float mInsertionMarkerTop = Float.NaN; 279 private float mInsertionMarkerBaseline = Float.NaN; 280 private float mInsertionMarkerBottom = Float.NaN; 281 private int mInsertionMarkerFlags = 0; 282 private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null; 283 private EditorBoundsInfo mEditorBoundsInfo = null; 284 private float[] mMatrixValues = null; 285 private boolean mMatrixInitialized = false; 286 287 /** 288 * Sets the text range of the selection. Calling this can be skipped if there is no 289 * selection. 290 */ setSelectionRange(final int newStart, final int newEnd)291 public Builder setSelectionRange(final int newStart, final int newEnd) { 292 mSelectionStart = newStart; 293 mSelectionEnd = newEnd; 294 return this; 295 } 296 297 /** 298 * Sets the text range of the composing text. Calling this can be skipped if there is 299 * no composing text. 300 * @param composingTextStart index where the composing text starts. 301 * @param composingText the entire composing text. 302 */ setComposingText(final int composingTextStart, final CharSequence composingText)303 public Builder setComposingText(final int composingTextStart, 304 final CharSequence composingText) { 305 mComposingTextStart = composingTextStart; 306 if (composingText == null) { 307 mComposingText = null; 308 } else { 309 // Make a snapshot of the given char sequence. 310 mComposingText = new SpannedString(composingText); 311 } 312 return this; 313 } 314 315 /** 316 * Sets the location of the text insertion point (zero width cursor) as a rectangle in 317 * local coordinates. Calling this can be skipped when there is no text insertion point; 318 * however if there is an insertion point, editors must call this method. 319 * @param horizontalPosition horizontal position of the insertion marker, in the local 320 * coordinates that will be transformed with the transformation matrix when rendered on the 321 * screen. This should be calculated or compatible with 322 * {@link Layout#getPrimaryHorizontal(int)}. 323 * @param lineTop vertical position of the insertion marker, in the local coordinates that 324 * will be transformed with the transformation matrix when rendered on the screen. This 325 * should be calculated or compatible with {@link Layout#getLineTop(int)}. 326 * @param lineBaseline vertical position of the insertion marker, in the local coordinates 327 * that will be transformed with the transformation matrix when rendered on the screen. This 328 * should be calculated or compatible with {@link Layout#getLineBaseline(int)}. 329 * @param lineBottom vertical position of the insertion marker, in the local coordinates 330 * that will be transformed with the transformation matrix when rendered on the screen. This 331 * should be calculated or compatible with {@link Layout#getLineBottom(int)}. 332 * @param flags flags of the insertion marker. See {@link #FLAG_HAS_VISIBLE_REGION} for 333 * example. 334 */ setInsertionMarkerLocation(final float horizontalPosition, final float lineTop, final float lineBaseline, final float lineBottom, final int flags)335 public Builder setInsertionMarkerLocation(final float horizontalPosition, 336 final float lineTop, final float lineBaseline, final float lineBottom, 337 final int flags){ 338 mInsertionMarkerHorizontal = horizontalPosition; 339 mInsertionMarkerTop = lineTop; 340 mInsertionMarkerBaseline = lineBaseline; 341 mInsertionMarkerBottom = lineBottom; 342 mInsertionMarkerFlags = flags; 343 return this; 344 } 345 346 /** 347 * Adds the bounding box of the character specified with the index. 348 * 349 * @param index index of the character in Java chars units. Must be specified in 350 * ascending order across successive calls. 351 * @param left x coordinate of the left edge of the character in local coordinates. 352 * @param top y coordinate of the top edge of the character in local coordinates. 353 * @param right x coordinate of the right edge of the character in local coordinates. 354 * @param bottom y coordinate of the bottom edge of the character in local coordinates. 355 * @param flags flags for this character bounds. See {@link #FLAG_HAS_VISIBLE_REGION}, 356 * {@link #FLAG_HAS_INVISIBLE_REGION} and {@link #FLAG_IS_RTL}. These flags must be 357 * specified when necessary. 358 * @throws IllegalArgumentException If the index is a negative value, or not greater than 359 * all of the previously called indices. 360 */ addCharacterBounds(final int index, final float left, final float top, final float right, final float bottom, final int flags)361 public Builder addCharacterBounds(final int index, final float left, final float top, 362 final float right, final float bottom, final int flags) { 363 if (index < 0) { 364 throw new IllegalArgumentException("index must not be a negative integer."); 365 } 366 if (mCharacterBoundsArrayBuilder == null) { 367 mCharacterBoundsArrayBuilder = new SparseRectFArrayBuilder(); 368 } 369 mCharacterBoundsArrayBuilder.append(index, left, top, right, bottom, flags); 370 return this; 371 } 372 373 /** 374 * Sets the current editor related bounds. 375 * 376 * @param bounds {@link EditorBoundsInfo} in local coordinates. 377 */ 378 @NonNull setEditorBoundsInfo(@ullable EditorBoundsInfo bounds)379 public Builder setEditorBoundsInfo(@Nullable EditorBoundsInfo bounds) { 380 mEditorBoundsInfo = bounds; 381 return this; 382 } 383 384 /** 385 * Sets the matrix that transforms local coordinates into screen coordinates. 386 * @param matrix transformation matrix from local coordinates into screen coordinates. null 387 * is interpreted as an identity matrix. 388 */ setMatrix(final Matrix matrix)389 public Builder setMatrix(final Matrix matrix) { 390 if (mMatrixValues == null) { 391 mMatrixValues = new float[9]; 392 } 393 (matrix != null ? matrix : Matrix.IDENTITY_MATRIX).getValues(mMatrixValues); 394 mMatrixInitialized = true; 395 return this; 396 } 397 398 /** 399 * @return {@link CursorAnchorInfo} using parameters in this {@link Builder}. 400 * @throws IllegalArgumentException if one or more positional parameters are specified but 401 * the coordinate transformation matrix is not provided via {@link #setMatrix(Matrix)}. 402 */ build()403 public CursorAnchorInfo build() { 404 if (!mMatrixInitialized) { 405 // Coordinate transformation matrix is mandatory when at least one positional 406 // parameter is specified. 407 final boolean hasCharacterBounds = (mCharacterBoundsArrayBuilder != null 408 && !mCharacterBoundsArrayBuilder.isEmpty()); 409 if (hasCharacterBounds 410 || !Float.isNaN(mInsertionMarkerHorizontal) 411 || !Float.isNaN(mInsertionMarkerTop) 412 || !Float.isNaN(mInsertionMarkerBaseline) 413 || !Float.isNaN(mInsertionMarkerBottom)) { 414 throw new IllegalArgumentException("Coordinate transformation matrix is " + 415 "required when positional parameters are specified."); 416 } 417 } 418 return CursorAnchorInfo.create(this); 419 } 420 421 /** 422 * Resets the internal state so that this instance can be reused to build another 423 * instance of {@link CursorAnchorInfo}. 424 */ reset()425 public void reset() { 426 mSelectionStart = -1; 427 mSelectionEnd = -1; 428 mComposingTextStart = -1; 429 mComposingText = null; 430 mInsertionMarkerFlags = 0; 431 mInsertionMarkerHorizontal = Float.NaN; 432 mInsertionMarkerTop = Float.NaN; 433 mInsertionMarkerBaseline = Float.NaN; 434 mInsertionMarkerBottom = Float.NaN; 435 mMatrixInitialized = false; 436 if (mCharacterBoundsArrayBuilder != null) { 437 mCharacterBoundsArrayBuilder.reset(); 438 } 439 mEditorBoundsInfo = null; 440 } 441 } 442 create(Builder builder)443 private static CursorAnchorInfo create(Builder builder) { 444 final SparseRectFArray characterBoundsArray = 445 builder.mCharacterBoundsArrayBuilder != null 446 ? builder.mCharacterBoundsArrayBuilder.build() 447 : null; 448 final float[] matrixValues = new float[9]; 449 if (builder.mMatrixInitialized) { 450 System.arraycopy(builder.mMatrixValues, 0, matrixValues, 0, 9); 451 } else { 452 Matrix.IDENTITY_MATRIX.getValues(matrixValues); 453 } 454 455 return new CursorAnchorInfo(builder.mSelectionStart, builder.mSelectionEnd, 456 builder.mComposingTextStart, builder.mComposingText, builder.mInsertionMarkerFlags, 457 builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop, 458 builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom, 459 characterBoundsArray, builder.mEditorBoundsInfo, matrixValues); 460 } 461 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)462 private CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart, 463 @Nullable CharSequence composingText, int insertionMarkerFlags, 464 float insertionMarkerHorizontal, float insertionMarkerTop, 465 float insertionMarkerBaseline, float insertionMarkerBottom, 466 @Nullable SparseRectFArray characterBoundsArray, 467 @Nullable EditorBoundsInfo editorBoundsInfo, 468 @NonNull float[] matrixValues) { 469 mSelectionStart = selectionStart; 470 mSelectionEnd = selectionEnd; 471 mComposingTextStart = composingTextStart; 472 mComposingText = composingText; 473 mInsertionMarkerFlags = insertionMarkerFlags; 474 mInsertionMarkerHorizontal = insertionMarkerHorizontal; 475 mInsertionMarkerTop = insertionMarkerTop; 476 mInsertionMarkerBaseline = insertionMarkerBaseline; 477 mInsertionMarkerBottom = insertionMarkerBottom; 478 mCharacterBoundsArray = characterBoundsArray; 479 mEditorBoundsInfo = editorBoundsInfo; 480 mMatrixValues = matrixValues; 481 482 // To keep hash function simple, we only use some complex objects for hash. 483 int hashCode = Objects.hashCode(mComposingText); 484 hashCode *= 31; 485 hashCode += Arrays.hashCode(mMatrixValues); 486 mHashCode = hashCode; 487 } 488 489 /** 490 * Creates a new instance of {@link CursorAnchorInfo} by applying {@code parentMatrix} to 491 * the coordinate transformation matrix. 492 * 493 * @param original {@link CursorAnchorInfo} to be cloned from. 494 * @param parentMatrix {@link Matrix} to be applied to {@code original.getMatrix()} 495 * @return A new instance of {@link CursorAnchorInfo} whose {@link CursorAnchorInfo#getMatrix()} 496 * returns {@code parentMatrix * original.getMatrix()}. 497 * @hide 498 */ createForAdditionalParentMatrix(CursorAnchorInfo original, @NonNull Matrix parentMatrix)499 public static CursorAnchorInfo createForAdditionalParentMatrix(CursorAnchorInfo original, 500 @NonNull Matrix parentMatrix) { 501 return new CursorAnchorInfo(original.mSelectionStart, original.mSelectionEnd, 502 original.mComposingTextStart, original.mComposingText, 503 original.mInsertionMarkerFlags, original.mInsertionMarkerHorizontal, 504 original.mInsertionMarkerTop, original.mInsertionMarkerBaseline, 505 original.mInsertionMarkerBottom, original.mCharacterBoundsArray, 506 original.mEditorBoundsInfo, computeMatrixValues(parentMatrix, original)); 507 } 508 509 /** 510 * Returns a float array that represents {@link Matrix} elements for 511 * {@code parentMatrix * info.getMatrix()}. 512 * 513 * @param parentMatrix {@link Matrix} to be multiplied. 514 * @param info {@link CursorAnchorInfo} to provide {@link Matrix} to be multiplied. 515 * @return {@code parentMatrix * info.getMatrix()}. 516 */ computeMatrixValues(@onNull Matrix parentMatrix, @NonNull CursorAnchorInfo info)517 private static float[] computeMatrixValues(@NonNull Matrix parentMatrix, 518 @NonNull CursorAnchorInfo info) { 519 if (parentMatrix.isIdentity()) { 520 return info.mMatrixValues; 521 } 522 523 final Matrix newMatrix = new Matrix(); 524 newMatrix.setValues(info.mMatrixValues); 525 newMatrix.postConcat(parentMatrix); 526 527 final float[] matrixValues = new float[9]; 528 newMatrix.getValues(matrixValues); 529 return matrixValues; 530 } 531 532 /** 533 * Returns the index where the selection starts. 534 * @return {@code -1} if there is no selection. 535 */ getSelectionStart()536 public int getSelectionStart() { 537 return mSelectionStart; 538 } 539 540 /** 541 * Returns the index where the selection ends. 542 * @return {@code -1} if there is no selection. 543 */ getSelectionEnd()544 public int getSelectionEnd() { 545 return mSelectionEnd; 546 } 547 548 /** 549 * Returns the index where the composing text starts. 550 * @return {@code -1} if there is no composing text. 551 */ getComposingTextStart()552 public int getComposingTextStart() { 553 return mComposingTextStart; 554 } 555 556 /** 557 * Returns the entire composing text. 558 * @return {@code null} if there is no composition. 559 */ getComposingText()560 public CharSequence getComposingText() { 561 return mComposingText; 562 } 563 564 /** 565 * Returns the flag of the insertion marker. 566 * @return the flag of the insertion marker. {@code 0} if no flag is specified. 567 */ getInsertionMarkerFlags()568 public int getInsertionMarkerFlags() { 569 return mInsertionMarkerFlags; 570 } 571 572 /** 573 * Returns the horizontal start of the insertion marker, in the local coordinates that will 574 * be transformed with {@link #getMatrix()} when rendered on the screen. 575 * @return x coordinate that is compatible with {@link Layout#getPrimaryHorizontal(int)}. 576 * Pay special care to RTL/LTR handling. 577 * {@code java.lang.Float.NaN} if not specified. 578 * @see Layout#getPrimaryHorizontal(int) 579 */ getInsertionMarkerHorizontal()580 public float getInsertionMarkerHorizontal() { 581 return mInsertionMarkerHorizontal; 582 } 583 584 /** 585 * Returns the vertical top position of the insertion marker, in the local coordinates that 586 * will be transformed with {@link #getMatrix()} when rendered on the screen. 587 * @return y coordinate that is compatible with {@link Layout#getLineTop(int)}. 588 * {@code java.lang.Float.NaN} if not specified. 589 */ getInsertionMarkerTop()590 public float getInsertionMarkerTop() { 591 return mInsertionMarkerTop; 592 } 593 594 /** 595 * Returns the vertical baseline position of the insertion marker, in the local coordinates 596 * that will be transformed with {@link #getMatrix()} when rendered on the screen. 597 * @return y coordinate that is compatible with {@link Layout#getLineBaseline(int)}. 598 * {@code java.lang.Float.NaN} if not specified. 599 */ getInsertionMarkerBaseline()600 public float getInsertionMarkerBaseline() { 601 return mInsertionMarkerBaseline; 602 } 603 604 /** 605 * Returns the vertical bottom position of the insertion marker, in the local coordinates 606 * that will be transformed with {@link #getMatrix()} when rendered on the screen. 607 * @return y coordinate that is compatible with {@link Layout#getLineBottom(int)}. 608 * {@code java.lang.Float.NaN} if not specified. 609 */ getInsertionMarkerBottom()610 public float getInsertionMarkerBottom() { 611 return mInsertionMarkerBottom; 612 } 613 614 /** 615 * Returns a new instance of {@link RectF} that indicates the location of the character 616 * specified with the index. 617 * @param index index of the character in a Java chars. 618 * @return the character bounds in local coordinates as a new instance of {@link RectF}. 619 */ getCharacterBounds(final int index)620 public RectF getCharacterBounds(final int index) { 621 if (mCharacterBoundsArray == null) { 622 return null; 623 } 624 return mCharacterBoundsArray.get(index); 625 } 626 627 /** 628 * Returns the flags associated with the character bounds specified with the index. 629 * @param index index of the character in a Java chars. 630 * @return {@code 0} if no flag is specified. 631 */ getCharacterBoundsFlags(final int index)632 public int getCharacterBoundsFlags(final int index) { 633 if (mCharacterBoundsArray == null) { 634 return 0; 635 } 636 return mCharacterBoundsArray.getFlags(index, 0); 637 } 638 639 /** 640 * Returns {@link EditorBoundsInfo} for the current editor, or {@code null} if IME is not 641 * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} 642 * or {@link InputConnection#CURSOR_UPDATE_MONITOR}. 643 */ 644 @Nullable getEditorBoundsInfo()645 public EditorBoundsInfo getEditorBoundsInfo() { 646 return mEditorBoundsInfo; 647 } 648 649 /** 650 * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation 651 * matrix that is to be applied other positional data in this class. 652 * @return a new instance (copy) of the transformation matrix. 653 */ getMatrix()654 public Matrix getMatrix() { 655 final Matrix matrix = new Matrix(); 656 matrix.setValues(mMatrixValues); 657 return matrix; 658 } 659 660 /** 661 * Used to make this class parcelable. 662 */ 663 public static final @android.annotation.NonNull Parcelable.Creator<CursorAnchorInfo> CREATOR 664 = new Parcelable.Creator<CursorAnchorInfo>() { 665 @Override 666 public CursorAnchorInfo createFromParcel(Parcel source) { 667 return new CursorAnchorInfo(source); 668 } 669 670 @Override 671 public CursorAnchorInfo[] newArray(int size) { 672 return new CursorAnchorInfo[size]; 673 } 674 }; 675 676 @Override describeContents()677 public int describeContents() { 678 return 0; 679 } 680 } 681