diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java index 7f07146776ab0..a17089169b614 100644 --- a/core/java/android/view/inputmethod/CursorAnchorInfo.java +++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java @@ -415,7 +415,7 @@ public final class CursorAnchorInfo implements Parcelable { "required when positional parameters are specified."); } } - return new CursorAnchorInfo(this); + return CursorAnchorInfo.create(this); } /** @@ -440,31 +440,93 @@ public final class CursorAnchorInfo implements Parcelable { } } - private CursorAnchorInfo(final Builder builder) { - mSelectionStart = builder.mSelectionStart; - mSelectionEnd = builder.mSelectionEnd; - mComposingTextStart = builder.mComposingTextStart; - mComposingText = builder.mComposingText; - mInsertionMarkerFlags = builder.mInsertionMarkerFlags; - mInsertionMarkerHorizontal = builder.mInsertionMarkerHorizontal; - mInsertionMarkerTop = builder.mInsertionMarkerTop; - mInsertionMarkerBaseline = builder.mInsertionMarkerBaseline; - mInsertionMarkerBottom = builder.mInsertionMarkerBottom; - mCharacterBoundsArray = builder.mCharacterBoundsArrayBuilder != null - ? builder.mCharacterBoundsArrayBuilder.build() : null; - mEditorBoundsInfo = builder.mEditorBoundsInfo; - mMatrixValues = new float[9]; + private static CursorAnchorInfo create(Builder builder) { + final SparseRectFArray characterBoundsArray = + builder.mCharacterBoundsArrayBuilder != null + ? builder.mCharacterBoundsArrayBuilder.build() + : null; + final float[] matrixValues = new float[9]; if (builder.mMatrixInitialized) { - System.arraycopy(builder.mMatrixValues, 0, mMatrixValues, 0, 9); + System.arraycopy(builder.mMatrixValues, 0, matrixValues, 0, 9); } else { - Matrix.IDENTITY_MATRIX.getValues(mMatrixValues); + Matrix.IDENTITY_MATRIX.getValues(matrixValues); } + return new CursorAnchorInfo(builder.mSelectionStart, builder.mSelectionEnd, + builder.mComposingTextStart, builder.mComposingText, builder.mInsertionMarkerFlags, + builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop, + builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom, + characterBoundsArray, builder.mEditorBoundsInfo, matrixValues); + } + + private 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) { + mSelectionStart = selectionStart; + mSelectionEnd = selectionEnd; + mComposingTextStart = composingTextStart; + mComposingText = composingText; + mInsertionMarkerFlags = insertionMarkerFlags; + mInsertionMarkerHorizontal = insertionMarkerHorizontal; + mInsertionMarkerTop = insertionMarkerTop; + mInsertionMarkerBaseline = insertionMarkerBaseline; + mInsertionMarkerBottom = insertionMarkerBottom; + mCharacterBoundsArray = characterBoundsArray; + mEditorBoundsInfo = editorBoundsInfo; + mMatrixValues = matrixValues; + // To keep hash function simple, we only use some complex objects for hash. - int hash = Objects.hashCode(mComposingText); - hash *= 31; - hash += Arrays.hashCode(mMatrixValues); - mHashCode = hash; + int hashCode = Objects.hashCode(mComposingText); + hashCode *= 31; + hashCode += Arrays.hashCode(mMatrixValues); + mHashCode = hashCode; + } + + /** + * Creates a new instance of {@link CursorAnchorInfo} by applying {@code parentMatrix} to + * the coordinate transformation matrix. + * + * @param original {@link CursorAnchorInfo} to be cloned from. + * @param parentMatrix {@link Matrix} to be applied to {@code original.getMatrix()} + * @return A new instance of {@link CursorAnchorInfo} whose {@link CursorAnchorInfo#getMatrix()} + * returns {@code parentMatrix * original.getMatrix()}. + * @hide + */ + public static CursorAnchorInfo createForAdditionalParentMatrix(CursorAnchorInfo original, + @NonNull Matrix parentMatrix) { + return new CursorAnchorInfo(original.mSelectionStart, original.mSelectionEnd, + original.mComposingTextStart, original.mComposingText, + original.mInsertionMarkerFlags, original.mInsertionMarkerHorizontal, + original.mInsertionMarkerTop, original.mInsertionMarkerBaseline, + original.mInsertionMarkerBottom, original.mCharacterBoundsArray, + original.mEditorBoundsInfo, computeMatrixValues(parentMatrix, original)); + } + + /** + * Returns a float array that represents {@link Matrix} elements for + * {@code parentMatrix * info.getMatrix()}. + * + * @param parentMatrix {@link Matrix} to be multiplied. + * @param info {@link CursorAnchorInfo} to provide {@link Matrix} to be multiplied. + * @return {@code parentMatrix * info.getMatrix()}. + */ + private static float[] computeMatrixValues(@NonNull Matrix parentMatrix, + @NonNull CursorAnchorInfo info) { + if (parentMatrix.isIdentity()) { + return info.mMatrixValues; + } + + final Matrix newMatrix = new Matrix(); + newMatrix.setValues(info.mMatrixValues); + newMatrix.postConcat(parentMatrix); + + final float[] matrixValues = new float[9]; + newMatrix.getValues(matrixValues); + return matrixValues; } /** diff --git a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java new file mode 100644 index 0000000000000..9d7d71d8d5396 --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import static org.junit.Assert.assertEquals; + +import android.graphics.Matrix; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class CursorAnchorInfoTest { + @Test + public void testCreateForAdditionalParentMatrix() { + final Matrix originalMatrix = new Matrix(); + originalMatrix.setTranslate(10.0f, 20.0f); + final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); + builder.setMatrix(originalMatrix); + + final CursorAnchorInfo originalInstance = builder.build(); + + assertEquals(originalMatrix, originalInstance.getMatrix()); + + final Matrix additionalParentMatrix = new Matrix(); + additionalParentMatrix.setTranslate(1.0f, 2.0f); + + final Matrix newMatrix = new Matrix(originalMatrix); + newMatrix.postConcat(additionalParentMatrix); + + builder.reset(); + builder.setMatrix(newMatrix); + // An instance created by the standard Builder class. + final CursorAnchorInfo newInstanceByBuilder = builder.build(); + + // An instance created by an @hide method. + final CursorAnchorInfo newInstanceByMethod = + CursorAnchorInfo.createForAdditionalParentMatrix( + originalInstance, additionalParentMatrix); + + assertEquals(newMatrix, newInstanceByBuilder.getMatrix()); + assertEquals(newMatrix, newInstanceByMethod.getMatrix()); + assertEquals(newInstanceByBuilder.hashCode(), newInstanceByMethod.hashCode()); + } +}