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