xref: /aosp_15_r20/cts/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view.inputmethod.cts;
18 
19 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
20 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
21 import static android.view.inputmethod.CursorAnchorInfo.FLAG_IS_RTL;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertNotEquals;
25 import static org.junit.Assert.assertNull;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.graphics.Color;
30 import android.graphics.Matrix;
31 import android.graphics.RectF;
32 import android.graphics.Typeface;
33 import android.graphics.text.LineBreakConfig;
34 import android.os.LocaleList;
35 import android.os.Parcel;
36 import android.platform.test.annotations.AppModeSdkSandbox;
37 import android.text.TextUtils;
38 import android.view.inputmethod.CursorAnchorInfo;
39 import android.view.inputmethod.CursorAnchorInfo.Builder;
40 import android.view.inputmethod.EditorBoundsInfo;
41 import android.view.inputmethod.TextAppearanceInfo;
42 
43 import androidx.test.filters.SmallTest;
44 
45 import com.android.compatibility.common.util.ApiTest;
46 
47 import org.junit.Test;
48 
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.List;
53 
54 @SmallTest
55 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
56 public final class CursorAnchorInfoTest {
57     private static final float EPSILON = 0.0000001f;
58 
59     private static final RectF[] MANY_BOUNDS = new RectF[] {
60             new RectF(101.0f, 201.0f, 301.0f, 401.0f),
61             new RectF(102.0f, 202.0f, 302.0f, 402.0f),
62             new RectF(103.0f, 203.0f, 303.0f, 403.0f),
63             new RectF(104.0f, 204.0f, 304.0f, 404.0f),
64             new RectF(105.0f, 205.0f, 305.0f, 405.0f),
65             new RectF(106.0f, 206.0f, 306.0f, 406.0f),
66             new RectF(107.0f, 207.0f, 307.0f, 407.0f),
67             new RectF(108.0f, 208.0f, 308.0f, 408.0f),
68             new RectF(109.0f, 209.0f, 309.0f, 409.0f),
69             new RectF(110.0f, 210.0f, 310.0f, 410.0f),
70             new RectF(111.0f, 211.0f, 311.0f, 411.0f),
71             new RectF(112.0f, 212.0f, 312.0f, 412.0f),
72             new RectF(113.0f, 213.0f, 313.0f, 413.0f),
73             new RectF(114.0f, 214.0f, 314.0f, 414.0f),
74             new RectF(115.0f, 215.0f, 315.0f, 415.0f),
75             new RectF(116.0f, 216.0f, 316.0f, 416.0f),
76             new RectF(117.0f, 217.0f, 317.0f, 417.0f),
77             new RectF(118.0f, 218.0f, 318.0f, 418.0f),
78             new RectF(119.0f, 219.0f, 319.0f, 419.0f),
79     };
80     private static final int[] MANY_FLAGS_ARRAY = new int[] {
81         FLAG_HAS_INVISIBLE_REGION,
82         FLAG_HAS_INVISIBLE_REGION | FLAG_HAS_VISIBLE_REGION,
83         FLAG_HAS_VISIBLE_REGION,
84         FLAG_HAS_VISIBLE_REGION,
85         FLAG_HAS_VISIBLE_REGION,
86         FLAG_HAS_VISIBLE_REGION,
87         FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
88         FLAG_HAS_INVISIBLE_REGION | FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
89         FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL,
90         FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
91         FLAG_HAS_VISIBLE_REGION,
92         FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
93         FLAG_HAS_VISIBLE_REGION,
94         FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
95         FLAG_HAS_VISIBLE_REGION,
96         FLAG_HAS_VISIBLE_REGION | FLAG_IS_RTL,
97         FLAG_HAS_VISIBLE_REGION,
98         FLAG_HAS_INVISIBLE_REGION,
99         FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL,
100     };
101 
102     @Test
103     @ApiTest(
104             apis = {
105                     "android.view.inputmethod.CursorAnchorInfo#getComposingText",
106                     "android.view.inputmethod.CursorAnchorInfo#getEditorBoundsInfo",
107                     "android.view.inputmethod.CursorAnchorInfo#getInsertionMarkerFlags",
108                     "android.view.inputmethod.CursorAnchorInfo#getInsertionMarkerHorizontal",
109                     "android.view.inputmethod.CursorAnchorInfo#getInsertionMarkerTop",
110                     "android.view.inputmethod.CursorAnchorInfo#getInsertionMarkerBaseline",
111                     "android.view.inputmethod.CursorAnchorInfo#getInsertionMarkerBottom",
112                     "android.view.inputmethod.CursorAnchorInfo#getSelectionStart",
113                     "android.view.inputmethod.CursorAnchorInfo#getSelectionEnd",
114                     "android.view.inputmethod.CursorAnchorInfo#getMatrix",
115                     "android.view.inputmethod.CursorAnchorInfo#getVisibleLineBounds",
116                     "android.view.inputmethod.CursorAnchorInfo#getTextAppearanceInfo",
117                     "android.view.inputmethod.CursorAnchorInfo.Builder#build",
118                     "android.view.inputmethod.CursorAnchorInfo.Builder#setComposingText",
119                     "android.view.inputmethod.CursorAnchorInfo.Builder#setEditorBoundsInfo",
120                     "android.view.inputmethod.CursorAnchorInfo.Builder#setMatrix",
121                     "android.view.inputmethod.CursorAnchorInfo.Builder#setInsertionMarkerLocation",
122                     "android.view.inputmethod.CursorAnchorInfo.Builder#setSelectinRange",
123                     "android.view.inputmethod.CursorAnchorInfo.Builder#setVisibleLineBounds",
124                     "android.view.inputmethod.CursorAnchorInfo.Builder#setTextAppearanceInfo",
125             }
126     )
testBuilder()127     public void testBuilder() {
128         final int selectionStart = 30;
129         final int selectionEnd = 40;
130         final int composingTextStart = 32;
131         final String composingText = "test";
132         final int insertionMarkerFlags =
133                 FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL;
134         final float insertionMarkerHorizontal = 10.5f;
135         final float insertionMarkerTop = 100.1f;
136         final float insertionMarkerBaseline = 110.4f;
137         final float insertionMarkerBottom = 111.0f;
138 
139         Matrix transformMatrix = new Matrix();
140         transformMatrix.setScale(10.0f, 20.0f);
141 
142         final EditorBoundsInfo boundsInfo =
143                 new EditorBoundsInfo.Builder().setEditorBounds(MANY_BOUNDS[0])
144                         .setHandwritingBounds(MANY_BOUNDS[1]).build();
145 
146         final TextAppearanceInfo textAppearanceInfo = createTextAppearanceInfoBuilder().build();
147 
148         final Builder builder = new Builder();
149         builder.setSelectionRange(selectionStart, selectionEnd)
150                 .setComposingText(composingTextStart, composingText)
151                 .setInsertionMarkerLocation(insertionMarkerHorizontal, insertionMarkerTop,
152                         insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags)
153                 .setMatrix(transformMatrix)
154                 .setEditorBoundsInfo(boundsInfo)
155                 .setTextAppearanceInfo(textAppearanceInfo);
156 
157         for (int i = 0; i < MANY_BOUNDS.length; i++) {
158             final RectF bounds = MANY_BOUNDS[i];
159             final int flags = MANY_FLAGS_ARRAY[i];
160             builder.addCharacterBounds(i, bounds.left, bounds.top, bounds.right, bounds.bottom,
161                     flags);
162             builder.addVisibleLineBounds(bounds.left, bounds.top, bounds.right, bounds.bottom);
163         }
164 
165         final CursorAnchorInfo info = builder.build();
166         assertEquals(selectionStart, info.getSelectionStart());
167         assertEquals(selectionEnd, info.getSelectionEnd());
168         assertEquals(composingTextStart, info.getComposingTextStart());
169         assertTrue(TextUtils.equals(composingText, info.getComposingText()));
170         assertEquals(insertionMarkerFlags, info.getInsertionMarkerFlags());
171         assertEquals(insertionMarkerHorizontal, info.getInsertionMarkerHorizontal(), EPSILON);
172         assertEquals(insertionMarkerTop, info.getInsertionMarkerTop(), EPSILON);
173         assertEquals(insertionMarkerBaseline, info.getInsertionMarkerBaseline(), EPSILON);
174         assertEquals(insertionMarkerBottom, info.getInsertionMarkerBottom(), EPSILON);
175         assertEquals(transformMatrix, info.getMatrix());
176         assertEquals(boundsInfo, info.getEditorBoundsInfo());
177         assertEquals(Arrays.asList(MANY_BOUNDS), info.getVisibleLineBounds());
178         assertEquals(MANY_BOUNDS[0],
179                 info.getEditorBoundsInfo().getEditorBounds());
180         assertEquals(MANY_BOUNDS[1],
181                 info.getEditorBoundsInfo().getHandwritingBounds());
182         for (int i = 0; i < MANY_BOUNDS.length; i++) {
183             final RectF expectedBounds = MANY_BOUNDS[i];
184             assertEquals(expectedBounds, info.getCharacterBounds(i));
185         }
186         assertNull(info.getCharacterBounds(-1));
187         assertNull(info.getCharacterBounds(MANY_BOUNDS.length + 1));
188         for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
189             final int expectedFlags = MANY_FLAGS_ARRAY[i];
190             assertEquals(expectedFlags, info.getCharacterBoundsFlags(i));
191         }
192         assertEquals(0, info.getCharacterBoundsFlags(-1));
193         assertEquals(0, info.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
194         assertEquals(textAppearanceInfo, info.getTextAppearanceInfo());
195         assertTextAppearanceInfoContentsEqual(info.getTextAppearanceInfo());
196 
197         // Make sure that the builder can reproduce the same object.
198         final CursorAnchorInfo info2 = builder.build();
199         assertEquals(selectionStart, info2.getSelectionStart());
200         assertEquals(selectionEnd, info2.getSelectionEnd());
201         assertEquals(composingTextStart, info2.getComposingTextStart());
202         assertTrue(TextUtils.equals(composingText, info2.getComposingText()));
203         assertEquals(insertionMarkerFlags, info2.getInsertionMarkerFlags());
204         assertEquals(insertionMarkerHorizontal, info2.getInsertionMarkerHorizontal(), EPSILON);
205         assertEquals(insertionMarkerTop, info2.getInsertionMarkerTop(), EPSILON);
206         assertEquals(insertionMarkerBaseline, info2.getInsertionMarkerBaseline(), EPSILON);
207         assertEquals(insertionMarkerBottom, info2.getInsertionMarkerBottom(), EPSILON);
208         assertEquals(boundsInfo, info2.getEditorBoundsInfo());
209         assertEquals(Arrays.asList(MANY_BOUNDS), info2.getVisibleLineBounds());
210         assertEquals(transformMatrix, info2.getMatrix());
211         for (int i = 0; i < MANY_BOUNDS.length; i++) {
212             final RectF expectedBounds = MANY_BOUNDS[i];
213             assertEquals(expectedBounds, info2.getCharacterBounds(i));
214         }
215         assertNull(info2.getCharacterBounds(-1));
216         assertNull(info2.getCharacterBounds(MANY_BOUNDS.length + 1));
217         for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
218             final int expectedFlags = MANY_FLAGS_ARRAY[i];
219             assertEquals(expectedFlags, info2.getCharacterBoundsFlags(i));
220         }
221         assertEquals(0, info2.getCharacterBoundsFlags(-1));
222         assertEquals(0, info2.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
223         assertEquals(textAppearanceInfo, info2.getTextAppearanceInfo());
224         assertTextAppearanceInfoContentsEqual(info2.getTextAppearanceInfo());
225         assertEquals(info, info2);
226         assertEquals(info.hashCode(), info2.hashCode());
227 
228         // Make sure that object can be marshaled via Parcel.
229         final CursorAnchorInfo info3 = cloneViaParcel(info2);
230         assertEquals(selectionStart, info3.getSelectionStart());
231         assertEquals(selectionEnd, info3.getSelectionEnd());
232         assertEquals(composingTextStart, info3.getComposingTextStart());
233         assertTrue(TextUtils.equals(composingText, info3.getComposingText()));
234         assertEquals(insertionMarkerFlags, info3.getInsertionMarkerFlags());
235         assertEquals(insertionMarkerHorizontal, info3.getInsertionMarkerHorizontal(), EPSILON);
236         assertEquals(insertionMarkerTop, info3.getInsertionMarkerTop(), EPSILON);
237         assertEquals(insertionMarkerBaseline, info3.getInsertionMarkerBaseline(), EPSILON);
238         assertEquals(insertionMarkerBottom, info3.getInsertionMarkerBottom(), EPSILON);
239         assertEquals(boundsInfo, info3.getEditorBoundsInfo());
240         assertEquals(Arrays.asList(MANY_BOUNDS), info3.getVisibleLineBounds());
241         assertEquals(transformMatrix, info3.getMatrix());
242         for (int i = 0; i < MANY_BOUNDS.length; i++) {
243             final RectF expectedBounds = MANY_BOUNDS[i];
244             assertEquals(expectedBounds, info3.getCharacterBounds(i));
245         }
246         assertNull(info3.getCharacterBounds(-1));
247         assertNull(info3.getCharacterBounds(MANY_BOUNDS.length + 1));
248         for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
249             final int expectedFlags = MANY_FLAGS_ARRAY[i];
250             assertEquals(expectedFlags, info3.getCharacterBoundsFlags(i));
251         }
252         assertEquals(0, info3.getCharacterBoundsFlags(-1));
253         assertEquals(0, info3.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
254         assertEquals(textAppearanceInfo, info3.getTextAppearanceInfo());
255         assertTextAppearanceInfoContentsEqual(info3.getTextAppearanceInfo());
256         assertEquals(info.hashCode(), info3.hashCode());
257 
258         builder.reset();
259         final CursorAnchorInfo uninitializedInfo = builder.build();
260         assertEquals(-1, uninitializedInfo.getSelectionStart());
261         assertEquals(-1, uninitializedInfo.getSelectionEnd());
262         assertEquals(-1, uninitializedInfo.getComposingTextStart());
263         assertNull(uninitializedInfo.getComposingText());
264         assertEquals(0, uninitializedInfo.getInsertionMarkerFlags());
265         assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerHorizontal(), EPSILON);
266         assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerTop(), EPSILON);
267         assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBaseline(), EPSILON);
268         assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBottom(), EPSILON);
269         assertEquals(null, uninitializedInfo.getEditorBoundsInfo());
270         assertEquals(new ArrayList<>(), uninitializedInfo.getVisibleLineBounds());
271         assertEquals(new Matrix(), uninitializedInfo.getMatrix());
272         assertEquals(null, uninitializedInfo.getTextAppearanceInfo());
273     }
274 
275     @Test
276     @ApiTest(
277             apis = {
278                     "android.view.inputmethod.CursorAnchorInfo.Builder#build",
279                     "android.view.inputmethod.CursorAnchorInfo.Builder#setComposingText",
280                     "android.view.inputmethod.CursorAnchorInfo.Builder#setMatrix",
281                     "android.view.inputmethod.CursorAnchorInfo.Builder#setInsertionMarkerLocation",
282                     "android.view.inputmethod.CursorAnchorInfo.Builder#setSelectinRange",
283                     "android.view.inputmethod.CursorAnchorInfo.Builder#setVisibleLineBounds",
284                     "android.view.inputmethod.CursorAnchorInfo.Builder#setTextAppearanceInfo",
285             }
286     )
testEquality()287     public void testEquality() {
288         final Matrix matrix1 = new Matrix();
289         matrix1.setTranslate(10.0f, 20.0f);
290         final Matrix matrix2 = new Matrix();
291         matrix2.setTranslate(110.0f, 120.0f);
292         final Matrix nanMatrix = new Matrix();
293         nanMatrix.setValues(new float[]{
294                 Float.NaN, Float.NaN, Float.NaN,
295                 Float.NaN, Float.NaN, Float.NaN,
296                 Float.NaN, Float.NaN, Float.NaN});
297         final int selectionStart1 = 2;
298         final int selectionEnd1 = 7;
299         final String composingText1 = "0123456789";
300         final int composingTextStart1 = 0;
301         final int insertionMarkerFlags1 = FLAG_HAS_VISIBLE_REGION;
302         final float insertionMarkerHorizontal1 = 10.5f;
303         final float insertionMarkerTop1 = 100.1f;
304         final float insertionMarkerBaseline1 = 110.4f;
305         final float insertionMarkerBottom1 = 111.0f;
306         final int selectionStart2 = 4;
307         final int selectionEnd2 = 8;
308         final String composingText2 = "9876543210";
309         final int composingTextStart2 = 3;
310         final int insertionMarkerFlags2 =
311                 FLAG_HAS_VISIBLE_REGION | FLAG_HAS_INVISIBLE_REGION | FLAG_IS_RTL;
312         final float insertionMarkerHorizontal2 = 14.5f;
313         final float insertionMarkerTop2 = 200.1f;
314         final float insertionMarkerBaseline2 = 210.4f;
315         final float insertionMarkerBottom2 = 211.0f;
316         final List<RectF> visibleLineBounds1 = Arrays.asList(MANY_BOUNDS[0], MANY_BOUNDS[1]);
317         final List<RectF> visibleLineBounds2 = Arrays.asList(MANY_BOUNDS[2], MANY_BOUNDS[3]);
318 
319         // Default instance should be equal.
320         assertEquals(new Builder().build(), new Builder().build());
321 
322         assertEquals(
323                 new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
324                 new Builder().setSelectionRange(selectionStart1, selectionEnd1).build());
325         assertNotEquals(
326                 new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
327                 new Builder().setSelectionRange(selectionStart1, selectionEnd2).build());
328         assertNotEquals(
329                 new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
330                 new Builder().setSelectionRange(selectionStart2, selectionEnd1).build());
331         assertNotEquals(
332                 new Builder().setSelectionRange(selectionStart1, selectionEnd1).build(),
333                 new Builder().setSelectionRange(selectionStart2, selectionEnd2).build());
334         assertEquals(
335                 new Builder().setComposingText(composingTextStart1, composingText1).build(),
336                 new Builder().setComposingText(composingTextStart1, composingText1).build());
337         assertNotEquals(
338                 new Builder().setComposingText(composingTextStart1, composingText1).build(),
339                 new Builder().setComposingText(composingTextStart2, composingText1).build());
340         assertNotEquals(
341                 new Builder().setComposingText(composingTextStart1, composingText1).build(),
342                 new Builder().setComposingText(composingTextStart1, composingText2).build());
343         assertNotEquals(
344                 new Builder().setComposingText(composingTextStart1, composingText1).build(),
345                 new Builder().setComposingText(composingTextStart2, composingText2).build());
346 
347         // For insertion marker locations, Float#NaN is treated as if it was a number.
348         assertEquals(
349                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
350                         Float.NaN, Float.NaN, Float.NaN, Float.NaN,
351                         insertionMarkerFlags1).build(),
352                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
353                         Float.NaN, Float.NaN, Float.NaN, Float.NaN,
354                         insertionMarkerFlags1).build());
355 
356         // Check Matrix.
357         assertEquals(
358                 new Builder().setMatrix(matrix1).build(),
359                 new Builder().setMatrix(matrix1).build());
360         assertNotEquals(
361                 new Builder().setMatrix(matrix1).build(),
362                 new Builder().setMatrix(matrix2).build());
363         assertNotEquals(
364                 new Builder().setMatrix(matrix1).build(),
365                 new Builder().setMatrix(nanMatrix).build());
366         // Unlike insertion marker locations, Float#NaN in the matrix is treated as just a NaN as
367         // usual (NaN == NaN -> false).
368         assertNotEquals(
369                 new Builder().setMatrix(nanMatrix).build(),
370                 new Builder().setMatrix(nanMatrix).build());
371 
372         assertEquals(
373                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
374                         insertionMarkerHorizontal1, insertionMarkerTop1,
375                         insertionMarkerBaseline1, insertionMarkerBottom1,
376                         insertionMarkerFlags1).build(),
377                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
378                         insertionMarkerHorizontal1, insertionMarkerTop1,
379                         insertionMarkerBaseline1, insertionMarkerBottom1,
380                         insertionMarkerFlags1).build());
381         assertNotEquals(
382                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
383                         Float.NaN, insertionMarkerTop1,
384                         insertionMarkerBaseline1, insertionMarkerBottom1,
385                         insertionMarkerFlags1).build(),
386                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
387                         insertionMarkerHorizontal1, insertionMarkerTop1,
388                         insertionMarkerBaseline1, insertionMarkerBottom1,
389                         insertionMarkerFlags1).build());
390         assertNotEquals(
391                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
392                         insertionMarkerHorizontal1, insertionMarkerTop1,
393                         insertionMarkerBaseline1, insertionMarkerBottom1,
394                         insertionMarkerFlags1).build(),
395                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
396                         insertionMarkerHorizontal2, insertionMarkerTop1,
397                         insertionMarkerBaseline1, insertionMarkerBottom1,
398                         insertionMarkerFlags1).build());
399         assertNotEquals(
400                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
401                         insertionMarkerHorizontal1, insertionMarkerTop1,
402                         insertionMarkerBaseline1, insertionMarkerBottom1,
403                         insertionMarkerFlags1).build(),
404                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
405                         insertionMarkerHorizontal1, insertionMarkerTop2,
406                         insertionMarkerBaseline1, insertionMarkerBottom1,
407                         insertionMarkerFlags1).build());
408         assertNotEquals(
409                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
410                         insertionMarkerHorizontal1, insertionMarkerTop1,
411                         insertionMarkerBaseline1, insertionMarkerBottom1,
412                         insertionMarkerFlags1).build(),
413                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
414                         insertionMarkerHorizontal1, insertionMarkerTop1,
415                         insertionMarkerBaseline2, insertionMarkerBottom1,
416                         insertionMarkerFlags1).build());
417         assertNotEquals(
418                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
419                         insertionMarkerHorizontal1, insertionMarkerTop1,
420                         insertionMarkerBaseline1, insertionMarkerBottom1,
421                         insertionMarkerFlags1).build(),
422                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
423                         insertionMarkerHorizontal2, insertionMarkerTop1,
424                         insertionMarkerBaseline1, insertionMarkerBottom1,
425                         insertionMarkerFlags1).build());
426         assertNotEquals(
427                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
428                         insertionMarkerHorizontal1, insertionMarkerTop1,
429                         insertionMarkerBaseline1, insertionMarkerBottom1,
430                         insertionMarkerFlags1).build(),
431                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
432                         insertionMarkerHorizontal1, insertionMarkerTop1,
433                         insertionMarkerBaseline1, insertionMarkerBottom2,
434                         insertionMarkerFlags1).build());
435         assertNotEquals(
436                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
437                         insertionMarkerHorizontal1, insertionMarkerTop1,
438                         insertionMarkerBaseline1, insertionMarkerBottom1,
439                         insertionMarkerFlags1).build(),
440                 new Builder().setMatrix(matrix1).setInsertionMarkerLocation(
441                         insertionMarkerHorizontal1, insertionMarkerTop1,
442                         insertionMarkerBaseline1, insertionMarkerBottom1,
443                         insertionMarkerFlags2).build());
444         {
445             CursorAnchorInfo.Builder builder1 = new Builder().setMatrix(matrix1);
446             for (RectF rectF : visibleLineBounds1) {
447                 builder1.addVisibleLineBounds(rectF.left, rectF.top, rectF.right, rectF.bottom);
448             }
449 
450             CursorAnchorInfo.Builder builder2 = new Builder().setMatrix(matrix1);
451             for (RectF rectF : visibleLineBounds1) {
452                 builder2.addVisibleLineBounds(rectF.left, rectF.top, rectF.right, rectF.bottom);
453             }
454 
455             assertEquals(builder1.build(), builder2.build());
456         }
457         {
458             CursorAnchorInfo.Builder builder1 = new Builder().setMatrix(matrix1);
459             for (RectF rectF : visibleLineBounds1) {
460                 builder1.addVisibleLineBounds(rectF.left, rectF.top, rectF.right, rectF.bottom);
461             }
462 
463             CursorAnchorInfo.Builder builder2 = new Builder().setMatrix(matrix1);
464             for (RectF rectF : visibleLineBounds2) {
465                 builder2.addVisibleLineBounds(rectF.left, rectF.top, rectF.right, rectF.bottom);
466             }
467 
468             assertNotEquals(builder1.build(), builder2.build());
469         }
470         {
471             TextAppearanceInfo.Builder appearanceBuilder1 = createTextAppearanceInfoBuilder();
472             TextAppearanceInfo.Builder appearanceBuilder2 = createTextAppearanceInfoBuilder();
473             TextAppearanceInfo.Builder appearanceBuilder3 = createTextAppearanceInfoBuilder();
474             appearanceBuilder3.setTextSize(30f);
475 
476             TextAppearanceInfo textAppearanceInfo1 = appearanceBuilder1.build();
477             TextAppearanceInfo textAppearanceInfo2 = appearanceBuilder2.build();
478             TextAppearanceInfo textAppearanceInfo3 = appearanceBuilder3.build();
479 
480             assertEquals(new Builder().setTextAppearanceInfo(textAppearanceInfo1).build(),
481                     new Builder().setTextAppearanceInfo(textAppearanceInfo1).build());
482             assertEquals(new Builder().setTextAppearanceInfo(textAppearanceInfo1).build(),
483                     new Builder().setTextAppearanceInfo(textAppearanceInfo2).build());
484             assertNotEquals(new Builder().setTextAppearanceInfo(textAppearanceInfo1).build(),
485                     new Builder().setTextAppearanceInfo(textAppearanceInfo3).build());
486         }
487     }
488 
489     @Test
testMatrixIsCopied()490     public void testMatrixIsCopied() {
491         final Matrix matrix1 = new Matrix();
492         matrix1.setTranslate(10.0f, 20.0f);
493         final Matrix matrix2 = new Matrix();
494         matrix2.setTranslate(110.0f, 120.0f);
495         final Matrix matrix3 = new Matrix();
496         matrix3.setTranslate(210.0f, 220.0f);
497         final Matrix matrix = new Matrix();
498         final Builder builder = new Builder();
499 
500         matrix.set(matrix1);
501         builder.setMatrix(matrix);
502         matrix.postRotate(90.0f);
503 
504         final CursorAnchorInfo firstInstance = builder.build();
505         assertEquals(matrix1, firstInstance.getMatrix());
506         matrix.set(matrix2);
507         builder.setMatrix(matrix);
508         final CursorAnchorInfo secondInstance = builder.build();
509         assertEquals(matrix1, firstInstance.getMatrix());
510         assertEquals(matrix2, secondInstance.getMatrix());
511 
512         matrix.set(matrix3);
513         assertEquals(matrix1, firstInstance.getMatrix());
514         assertEquals(matrix2, secondInstance.getMatrix());
515     }
516 
517     @Test
testMatrixIsRequired()518     public void testMatrixIsRequired() {
519         final int selectionStart = 30;
520         final int selectionEnd = 40;
521         final int composingTextStart = 32;
522         final String composingText = "test";
523         final int insertionMarkerFlags = FLAG_HAS_VISIBLE_REGION;
524         final float insertionMarkerHorizontal = 10.5f;
525         final float insertionMarkerTop = 100.1f;
526         final float insertionMarkerBaseline = 110.4f;
527         final float insertionMarkerBottom = 111.0f;
528         Matrix transformMatrix = new Matrix();
529         transformMatrix.setScale(10.0f, 20.0f);
530 
531         final Builder builder = new Builder();
532         // Check twice to make sure if Builder#reset() works as expected.
533         for (int repeatCount = 0; repeatCount < 2; ++repeatCount) {
534             builder.setSelectionRange(selectionStart, selectionEnd)
535                     .setComposingText(composingTextStart, composingText);
536             try {
537                 // Should succeed as coordinate transformation matrix is not required if no
538                 // positional information is specified.
539                 builder.build();
540             } catch (IllegalArgumentException ex) {
541                 fail();
542             }
543 
544             builder.setInsertionMarkerLocation(insertionMarkerHorizontal, insertionMarkerTop,
545                     insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags);
546             try {
547                 // Coordinate transformation matrix is required if no positional information is
548                 // specified.
549                 builder.build();
550                 fail();
551             } catch (IllegalArgumentException ex) {
552             }
553 
554             builder.setMatrix(transformMatrix);
555             try {
556                 // Should succeed as coordinate transformation matrix is required.
557                 builder.build();
558             } catch (IllegalArgumentException ex) {
559                 fail();
560             }
561 
562             builder.reset();
563         }
564     }
565 
566     @Test
567     @ApiTest(apis = "android.view.inputmethod.CursorAnchorInfo.Builder#setVisibleLineBounds")
testMatrixIsRequiredForVisibleLineBounds()568     public void testMatrixIsRequiredForVisibleLineBounds() {
569         final List<RectF> visibleLineBounds = Arrays.asList(MANY_BOUNDS);
570         final Matrix transformMatrix = new Matrix();
571 
572         final Builder builder = new Builder();
573         try {
574             // Should succeed as coordinate transformation matrix is not required if no
575             // positional information is specified.
576             builder.build();
577         } catch (IllegalArgumentException ex) {
578             fail();
579         }
580 
581         for (RectF rectF: visibleLineBounds) {
582             builder.addVisibleLineBounds(rectF.left, rectF.top, rectF.right, rectF.bottom);
583         }
584         try {
585             // Should fail since visible line bounds requires coordinates transformation matrix.
586             builder.build();
587             fail();
588         } catch (IllegalArgumentException ex) {
589         }
590 
591         builder.setMatrix(transformMatrix);
592         try {
593             // Should succeed as coordinate transformation matrix is provided.
594             builder.build();
595         } catch (IllegalArgumentException ex) {
596             fail();
597         }
598     }
599 
600     @Test
601     @ApiTest(apis = "android.view.inputmethod.CursorAnchorInfo.Builder#addCharacterBounds")
testBuilderAddCharacterBounds()602     public void testBuilderAddCharacterBounds() {
603         // A negative index should be rejected.
604         try {
605             new Builder().addCharacterBounds(-1, 0.0f, 0.0f, 0.0f, 0.0f, FLAG_HAS_VISIBLE_REGION);
606             fail();
607         } catch (IllegalArgumentException ex) {
608         }
609     }
610 
611     @Test
612     @ApiTest(apis = "android.view.inputmethod.CursorAnchorInfo.Builder#clearVisibleLineBounds")
testBuilderClearVisibleLineBounds()613     public void testBuilderClearVisibleLineBounds() {
614         CursorAnchorInfo.Builder builder = new Builder().setMatrix(Matrix.IDENTITY_MATRIX);
615         for (RectF rectF: MANY_BOUNDS) {
616             builder.addVisibleLineBounds(rectF.left, rectF.top, rectF.right, rectF.bottom);
617         }
618 
619         // Making sure visible line bounds are added correctly.
620         assertEquals(Arrays.asList(MANY_BOUNDS), builder.build().getVisibleLineBounds());
621 
622         builder.clearVisibleLineBounds();
623         // No visible line bounds is returned after clearVisibleLineBounds is called.
624         assertEquals(Collections.emptyList(), builder.build().getVisibleLineBounds());
625 
626         // Add more line bounds and verify again.
627         List<RectF> rectFs = Arrays.asList(MANY_BOUNDS[1], MANY_BOUNDS[0]);
628 
629         for (RectF rectF: rectFs) {
630             builder.addVisibleLineBounds(rectF.left, rectF.top, rectF.right, rectF.bottom);
631         }
632         assertEquals(rectFs, builder.build().getVisibleLineBounds());
633     }
634 
635     @Test
636     @ApiTest(apis = {"android.view.inputmethod.CursorAnchorInfo#getTextAppearanceInfo",
637             "android.view.inputmethod.CursorAnchorInfo.Builder#setTextAppearanceInfo"})
testTextAppearanceInfoWithEmptyEditText()638     public void testTextAppearanceInfoWithEmptyEditText() {
639         TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo.Builder().build();
640         CursorAnchorInfo.Builder builder = new Builder().setTextAppearanceInfo(textAppearanceInfo);
641         CursorAnchorInfo info1 = builder.build();
642         CursorAnchorInfo info2 = builder.build();
643         CursorAnchorInfo info3 = cloneViaParcel(info2);
644         assertEquals(textAppearanceInfo, info1.getTextAppearanceInfo());
645         assertEquals(textAppearanceInfo, info2.getTextAppearanceInfo());
646         assertEquals(textAppearanceInfo, info3.getTextAppearanceInfo());
647     }
648 
cloneViaParcel(CursorAnchorInfo src)649     private static CursorAnchorInfo cloneViaParcel(CursorAnchorInfo src) {
650         Parcel parcel = null;
651         try {
652             parcel = Parcel.obtain();
653             src.writeToParcel(parcel, 0);
654             parcel.setDataPosition(0);
655             return new CursorAnchorInfo(parcel);
656         } finally {
657             if (parcel != null) {
658                 parcel.recycle();
659             }
660         }
661     }
662 
createTextAppearanceInfoBuilder()663     private TextAppearanceInfo.Builder createTextAppearanceInfoBuilder() {
664         TextAppearanceInfo.Builder builder = new TextAppearanceInfo.Builder()
665                 .setTextSize(16.5f)
666                 .setTextLocales(LocaleList.forLanguageTags("en,ja"))
667                 .setSystemFontFamilyName("sans-serif")
668                 .setTextFontWeight(10)
669                 .setTextStyle(Typeface.ITALIC)
670                 .setAllCaps(true)
671                 .setShadowDx(2.0f)
672                 .setShadowDy(2.0f)
673                 .setShadowRadius(2.0f)
674                 .setShadowColor(Color.GRAY)
675                 .setElegantTextHeight(true)
676                 .setFallbackLineSpacing(true)
677                 .setLetterSpacing(5.0f)
678                 .setFontFeatureSettings("smcp")
679                 .setFontVariationSettings("'wdth' 1.0")
680                 .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE)
681                 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
682                 .setTextScaleX(1.5f)
683                 .setHighlightTextColor(Color.YELLOW)
684                 .setTextColor(Color.RED)
685                 .setHintTextColor(Color.GREEN)
686                 .setLinkTextColor(Color.BLUE);
687         return builder;
688     }
689 
assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo)690     private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) {
691         assertEquals(textAppearanceInfo.getTextSize(), 16.5f, EPSILON);
692         assertEquals(textAppearanceInfo.getTextLocales(), LocaleList.forLanguageTags("en,ja"));
693         assertEquals(textAppearanceInfo.getSystemFontFamilyName(), "sans-serif");
694         assertEquals(textAppearanceInfo.getTextFontWeight(), 10);
695         assertEquals(textAppearanceInfo.getTextStyle(), Typeface.ITALIC);
696         assertTrue(textAppearanceInfo.isAllCaps());
697         assertEquals(textAppearanceInfo.getShadowRadius(), 2.0f, EPSILON);
698         assertEquals(textAppearanceInfo.getShadowDx(), 2.0f, EPSILON);
699         assertEquals(textAppearanceInfo.getShadowDy(), 2.0f, EPSILON);
700         assertEquals(textAppearanceInfo.getShadowColor(), Color.GRAY);
701         assertTrue(textAppearanceInfo.isElegantTextHeight());
702         assertTrue(textAppearanceInfo.isFallbackLineSpacing());
703         assertEquals(textAppearanceInfo.getLetterSpacing(), 5.0f, EPSILON);
704         assertEquals(textAppearanceInfo.getFontFeatureSettings(), "smcp");
705         assertEquals(textAppearanceInfo.getFontVariationSettings(), "'wdth' 1.0");
706         assertEquals(textAppearanceInfo.getLineBreakStyle(),
707                 LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
708         assertEquals(textAppearanceInfo.getLineBreakWordStyle(),
709                 LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE);
710         assertEquals(textAppearanceInfo.getTextScaleX(), 1.5f, EPSILON);
711         assertEquals(textAppearanceInfo.getHighlightTextColor(), Color.YELLOW);
712         assertEquals(textAppearanceInfo.getTextColor(), Color.RED);
713         assertEquals(textAppearanceInfo.getHintTextColor(), Color.GREEN);
714         assertEquals(textAppearanceInfo.getLinkTextColor(), Color.BLUE);
715     }
716 }
717