blob: 466a7cd3ed996ba3d9111ae120a8f1723dc4e410 [file] [log] [blame]
/*
* Copyright (C) 2019 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.cts;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Parcel;
import android.text.TextUtils;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.CursorAnchorInfo.Builder;
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 {
private static final float EPSILON = 0.0000001f;
private static final RectF[] MANY_BOUNDS = new RectF[] {
new RectF(101.0f, 201.0f, 301.0f, 401.0f),
new RectF(102.0f, 202.0f, 302.0f, 402.0f),
new RectF(103.0f, 203.0f, 303.0f, 403.0f),
new RectF(104.0f, 204.0f, 304.0f, 404.0f),
new RectF(105.0f, 205.0f, 305.0f, 405.0f),
new RectF(106.0f, 206.0f, 306.0f, 406.0f),
new RectF(107.0f, 207.0f, 307.0f, 407.0f),
new RectF(108.0f, 208.0f, 308.0f, 408.0f),
new RectF(109.0f, 209.0f, 309.0f, 409.0f),
new RectF(110.0f, 210.0f, 310.0f, 410.0f),
new RectF(111.0f, 211.0f, 311.0f, 411.0f),
new RectF(112.0f, 212.0f, 312.0f, 412.0f),
new RectF(113.0f, 213.0f, 313.0f, 413.0f),
new RectF(114.0f, 214.0f, 314.0f, 414.0f),
new RectF(115.0f, 215.0f, 315.0f, 415.0f),
new RectF(116.0f, 216.0f, 316.0f, 416.0f),
new RectF(117.0f, 217.0f, 317.0f, 417.0f),
new RectF(118.0f, 218.0f, 318.0f, 418.0f),
new RectF(119.0f, 219.0f, 319.0f, 419.0f),
};
private static final int[] MANY_FLAGS_ARRAY = new int[] {
FLAG_HAS_INVISIBLE_REGION,
FLAG_HAS_INVISIBLE_REGION | FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
FLAG_HAS_INVISIBLE_REGION | FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL,
FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
FLAG_HAS_VISIBLE_REGION,
FLAG_HAS_INVISIBLE_REGION,
FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL,
};
@Test
public void testBuilder() {
final int selectionStart = 30;
final int selectionEnd = 40;
final int composingTextStart = 32;
final String composingText = "test";
final int insertionMarkerFlags =
FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL;
final float insertionMarkerHorizontal = 10.5f;
final float insertionMarkerTop = 100.1f;
final float insertionMarkerBaseline = 110.4f;
final float insertionMarkerBottom = 111.0f;
Matrix transformMatrix = new Matrix();
transformMatrix.setScale(10.0f, 20.0f);
final Builder builder = new Builder();
builder.setSelectionRange(selectionStart, selectionEnd)
.setComposingText(composingTextStart, composingText)
.setInsertionMarkerLocation(insertionMarkerHorizontal, insertionMarkerTop,
insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags)
.setMatrix(transformMatrix);
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF bounds = MANY_BOUNDS[i];
final int flags = MANY_FLAGS_ARRAY[i];
builder.addCharacterBounds(i, bounds.left, bounds.top, bounds.right, bounds.bottom,
flags);
}
final CursorAnchorInfo info = builder.build();
assertEquals(selectionStart, info.getSelectionStart());
assertEquals(selectionEnd, info.getSelectionEnd());
assertEquals(composingTextStart, info.getComposingTextStart());
assertTrue(TextUtils.equals(composingText, info.getComposingText()));
assertEquals(insertionMarkerFlags, info.getInsertionMarkerFlags());
assertEquals(insertionMarkerHorizontal, info.getInsertionMarkerHorizontal(), EPSILON);
assertEquals(insertionMarkerTop, info.getInsertionMarkerTop(), EPSILON);
assertEquals(insertionMarkerBaseline, info.getInsertionMarkerBaseline(), EPSILON);
assertEquals(insertionMarkerBottom, info.getInsertionMarkerBottom(), EPSILON);
assertEquals(transformMatrix, info.getMatrix());
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF expectedBounds = MANY_BOUNDS[i];
assertEquals(expectedBounds, info.getCharacterBounds(i));
}
assertNull(info.getCharacterBounds(-1));
assertNull(info.getCharacterBounds(MANY_BOUNDS.length + 1));
for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
final int expectedFlags = MANY_FLAGS_ARRAY[i];
assertEquals(expectedFlags, info.getCharacterBoundsFlags(i));
}
assertEquals(0, info.getCharacterBoundsFlags(-1));
assertEquals(0, info.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
// Make sure that the builder can reproduce the same object.
final CursorAnchorInfo info2 = builder.build();
assertEquals(selectionStart, info2.getSelectionStart());
assertEquals(selectionEnd, info2.getSelectionEnd());
assertEquals(composingTextStart, info2.getComposingTextStart());
assertTrue(TextUtils.equals(composingText, info2.getComposingText()));
assertEquals(insertionMarkerFlags, info2.getInsertionMarkerFlags());
assertEquals(insertionMarkerHorizontal, info2.getInsertionMarkerHorizontal(), EPSILON);
assertEquals(insertionMarkerTop, info2.getInsertionMarkerTop(), EPSILON);
assertEquals(insertionMarkerBaseline, info2.getInsertionMarkerBaseline(), EPSILON);
assertEquals(insertionMarkerBottom, info2.getInsertionMarkerBottom(), EPSILON);
assertEquals(transformMatrix, info2.getMatrix());
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF expectedBounds = MANY_BOUNDS[i];
assertEquals(expectedBounds, info2.getCharacterBounds(i));
}
assertNull(info2.getCharacterBounds(-1));
assertNull(info2.getCharacterBounds(MANY_BOUNDS.length + 1));
for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
final int expectedFlags = MANY_FLAGS_ARRAY[i];
assertEquals(expectedFlags, info2.getCharacterBoundsFlags(i));
}
assertEquals(0, info2.getCharacterBoundsFlags(-1));
assertEquals(0, info2.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
assertEquals(info, info2);
assertEquals(info.hashCode(), info2.hashCode());
// Make sure that object can be marshaled via Parcel.
final CursorAnchorInfo info3 = cloneViaParcel(info2);
assertEquals(selectionStart, info3.getSelectionStart());
assertEquals(selectionEnd, info3.getSelectionEnd());
assertEquals(composingTextStart, info3.getComposingTextStart());
assertTrue(TextUtils.equals(composingText, info3.getComposingText()));
assertEquals(insertionMarkerFlags, info3.getInsertionMarkerFlags());
assertEquals(insertionMarkerHorizontal, info3.getInsertionMarkerHorizontal(), EPSILON);
assertEquals(insertionMarkerTop, info3.getInsertionMarkerTop(), EPSILON);
assertEquals(insertionMarkerBaseline, info3.getInsertionMarkerBaseline(), EPSILON);
assertEquals(insertionMarkerBottom, info3.getInsertionMarkerBottom(), EPSILON);
assertEquals(transformMatrix, info3.getMatrix());
for (int i = 0; i < MANY_BOUNDS.length; i++) {
final RectF expectedBounds = MANY_BOUNDS[i];
assertEquals(expectedBounds, info3.getCharacterBounds(i));
}
assertNull(info3.getCharacterBounds(-1));
assertNull(info3.getCharacterBounds(MANY_BOUNDS.length + 1));
for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
final int expectedFlags = MANY_FLAGS_ARRAY[i];
assertEquals(expectedFlags, info3.getCharacterBoundsFlags(i));
}
assertEquals(0, info3.getCharacterBoundsFlags(-1));
assertEquals(0, info3.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
assertEquals(info.hashCode(), info3.hashCode());
builder.reset();
final CursorAnchorInfo uninitializedInfo = builder.build();
assertEquals(-1, uninitializedInfo.getSelectionStart());
assertEquals(-1, uninitializedInfo.getSelectionEnd());
assertEquals(-1, uninitializedInfo.getComposingTextStart());
assertNull(uninitializedInfo.getComposingText());
assertEquals(0, uninitializedInfo.getInsertionMarkerFlags());
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerHorizontal(), EPSILON);
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerTop(), EPSILON);
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBaseline(), EPSILON);
assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBottom(), EPSILON);
assertEquals(new Matrix(), uninitializedInfo.getMatrix());
}
@Test
public void testEquality() {
final Matrix matrix1 = new Matrix();
matrix1.setTranslate(10.0f, 20.0f);
final Matrix matrix2 = new Matrix();
matrix2.setTranslate(110.0f, 120.0f);
final Matrix nanMatrix = new Matrix();
nanMatrix.setValues(new float[]{
Float.NaN, Float.NaN, Float.NaN,
Float.NaN, Float.NaN, Float.NaN,
Float.NaN, Float.NaN, Float.NaN});
final int selectionStart1 = 2;
final int selectionEnd1 = 7;
final String composingText1 = "0123456789";
final int composingTextStart1 = 0;
final int insertionMarkerFlags1 = FLAG_HAS_VISIBLE_REGION;
final float insertionMarkerHorizontal1 = 10.5f;
final float insertionMarkerTop1 = 100.1f;
final float insertionMarkerBaseline1 = 110.4f;
final float insertionMarkerBottom1 = 111.0f;
final int selectionStart2 = 4;
final int selectionEnd2 = 8;
final String composingText2 = "9876543210";
final int composingTextStart2 = 3;
final int insertionMarkerFlags2 =
FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL;
final float insertionMarkerHorizontal2 = 14.5f;
final float insertionMarkerTop2 = 200.1f;
final float insertionMarkerBaseline2 = 210.4f;
final float insertionMarkerBottom2 = 211.0f;
// Default instance should be equal.
assertEquals(new Builder().build(), new Builder().build());
assertEquals(
new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
new Builder().setSelectionRange(selectionStart1, selectionEnd1).build());
assertNotEquals(
new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
new Builder().setSelectionRange(selectionStart1, selectionEnd2).build());
assertNotEquals(
new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
new Builder().setSelectionRange(selectionStart2, selectionEnd1).build());
assertNotEquals(
new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
new Builder().setSelectionRange(selectionStart2, selectionEnd2).build());
assertEquals(
new Builder().setComposingText(composingTextStart1, composingText1).build(),
new Builder().setComposingText(composingTextStart1, composingText1).build());
assertNotEquals(
new Builder().setComposingText(composingTextStart1, composingText1).build(),
new Builder().setComposingText(composingTextStart2, composingText1).build());
assertNotEquals(
new Builder().setComposingText(composingTextStart1, composingText1).build(),
new Builder().setComposingText(composingTextStart1, composingText2).build());
assertNotEquals(
new Builder().setComposingText(composingTextStart1, composingText1).build(),
new Builder().setComposingText(composingTextStart2, composingText2).build());
// For insertion marker locations, Float#NaN is treated as if it was a number.
assertEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
Float.NaN, Float.NaN, Float.NaN, Float.NaN,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
Float.NaN, Float.NaN, Float.NaN, Float.NaN,
insertionMarkerFlags1).build());
// Check Matrix.
assertEquals(
new Builder().setMatrix(matrix1).build(),
new Builder().setMatrix(matrix1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).build(),
new Builder().setMatrix(matrix2).build());
assertNotEquals(
new Builder().setMatrix(matrix1).build(),
new Builder().setMatrix(nanMatrix).build());
// Unlike insertion marker locations, Float#NaN in the matrix is treated as just a NaN as
// usual (NaN == NaN -> false).
assertNotEquals(
new Builder().setMatrix(nanMatrix).build(),
new Builder().setMatrix(nanMatrix).build());
assertEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
Float.NaN, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal2, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop2,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline2, insertionMarkerBottom1,
insertionMarkerFlags1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal2, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom2,
insertionMarkerFlags1).build());
assertNotEquals(
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags1).build(),
new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
insertionMarkerHorizontal1, insertionMarkerTop1,
insertionMarkerBaseline1, insertionMarkerBottom1,
insertionMarkerFlags2).build());
}
@Test
public void testMatrixIsCopied() {
final Matrix matrix1 = new Matrix();
matrix1.setTranslate(10.0f, 20.0f);
final Matrix matrix2 = new Matrix();
matrix2.setTranslate(110.0f, 120.0f);
final Matrix matrix3 = new Matrix();
matrix3.setTranslate(210.0f, 220.0f);
final Matrix matrix = new Matrix();
final Builder builder = new Builder();
matrix.set(matrix1);
builder.setMatrix(matrix);
matrix.postRotate(90.0f);
final CursorAnchorInfo firstInstance = builder.build();
assertEquals(matrix1, firstInstance.getMatrix());
matrix.set(matrix2);
builder.setMatrix(matrix);
final CursorAnchorInfo secondInstance = builder.build();
assertEquals(matrix1, firstInstance.getMatrix());
assertEquals(matrix2, secondInstance.getMatrix());
matrix.set(matrix3);
assertEquals(matrix1, firstInstance.getMatrix());
assertEquals(matrix2, secondInstance.getMatrix());
}
@Test
public void testMatrixIsRequired() {
final int selectionStart = 30;
final int selectionEnd = 40;
final int composingTextStart = 32;
final String composingText = "test";
final int insertionMarkerFlags = FLAG_HAS_VISIBLE_REGION;
final float insertionMarkerHorizontal = 10.5f;
final float insertionMarkerTop = 100.1f;
final float insertionMarkerBaseline = 110.4f;
final float insertionMarkerBottom = 111.0f;
Matrix transformMatrix = new Matrix();
transformMatrix.setScale(10.0f, 20.0f);
final Builder builder = new Builder();
// Check twice to make sure if Builder#reset() works as expected.
for (int repeatCount = 0; repeatCount < 2; ++repeatCount) {
builder.setSelectionRange(selectionStart, selectionEnd)
.setComposingText(composingTextStart, composingText);
try {
// Should succeed as coordinate transformation matrix is not required if no
// positional information is specified.
builder.build();
} catch (IllegalArgumentException ex) {
fail();
}
builder.setInsertionMarkerLocation(insertionMarkerHorizontal, insertionMarkerTop,
insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags);
try {
// Coordinate transformation matrix is required if no positional information is
// specified.
builder.build();
fail();
} catch (IllegalArgumentException ex) {
}
builder.setMatrix(transformMatrix);
try {
// Should succeed as coordinate transformation matrix is required.
builder.build();
} catch (IllegalArgumentException ex) {
fail();
}
builder.reset();
}
}
@Test
public void testBuilderAddCharacterBounds() {
// A negative index should be rejected.
try {
new Builder().addCharacterBounds(-1, 0.0f, 0.0f, 0.0f, 0.0f, FLAG_HAS_VISIBLE_REGION);
fail();
} catch (IllegalArgumentException ex) {
}
}
private static CursorAnchorInfo cloneViaParcel(CursorAnchorInfo src) {
Parcel parcel = null;
try {
parcel = Parcel.obtain();
src.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return new CursorAnchorInfo(parcel);
} finally {
if (parcel != null) {
parcel.recycle();
}
}
}
}