1 /*
2  * Copyright (C) 2022 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 org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertThrows;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 
24 import android.graphics.Matrix;
25 import android.graphics.RectF;
26 import android.os.Parcel;
27 import android.platform.test.annotations.AppModeSdkSandbox;
28 import android.text.SegmentFinder;
29 import android.view.inputmethod.TextBoundsInfo;
30 import android.view.inputmethod.TextBoundsInfoResult;
31 
32 import androidx.test.filters.SmallTest;
33 import androidx.test.runner.AndroidJUnit4;
34 
35 import com.android.compatibility.common.util.ApiTest;
36 
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 @SmallTest
41 @RunWith(AndroidJUnit4.class)
42 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
43 public class TextBoundsInfoTest {
44     private static final float[] CHARACTER_BOUNDS1 = new float[] {
45             0.0f, 0.0f, 10.0f, 20.0f,
46             10.0f, 0.0f, 20.0f, 20.0f,
47             20.0f, 0.0f, 30.0f, 20.0f,
48             10.0f, 20.0f, 20.0f, 40.0f,
49             0.0f, 20.0f, 10.0f, 40.0f};
50 
51     private static final float[] CHARACTER_BOUNDS2 = new float[] {
52             20.0f, 0.0f, 30.0f, 20.0f,
53             10.0f, 0.0f, 20.0f, 20.0f,
54             0.0f, 0.0f, 10.0f, 20.0f,
55             0.0f, 20.0f, 10.0f, 40.0f,
56             10.0f, 20.0f, 20.0f, 40.0f};
57 
58     private static final int LTR_BIDI_LEVEL = 0;
59     private static final int RTL_BIDI_LEVEL = 1;
60     private static final int MAX_BIDI_LEVEL = 125;
61     private static final int[] CHARACTER_BIDI_LEVEL1 = new int[] {LTR_BIDI_LEVEL, LTR_BIDI_LEVEL,
62             LTR_BIDI_LEVEL, RTL_BIDI_LEVEL, MAX_BIDI_LEVEL};
63     private static final int[] CHARACTER_BIDI_LEVEL2 = new int[] {MAX_BIDI_LEVEL, RTL_BIDI_LEVEL,
64             LTR_BIDI_LEVEL, LTR_BIDI_LEVEL, LTR_BIDI_LEVEL};
65     private static final int[] CHARACTER_FLAGS1 = new int[] {
66             TextBoundsInfo.FLAG_CHARACTER_WHITESPACE,
67             TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION,
68             TextBoundsInfo.FLAG_CHARACTER_LINEFEED,
69             TextBoundsInfo.FLAG_LINE_IS_RTL,
70             TextBoundsInfo.FLAG_LINE_IS_RTL
71     };
72 
73     private static final int[] CHARACTER_FLAGS2 = new int[] {TextBoundsInfo.FLAG_LINE_IS_RTL,
74             TextBoundsInfo.FLAG_LINE_IS_RTL, TextBoundsInfo.FLAG_CHARACTER_WHITESPACE, 0, 0};
75 
76     private static final SegmentFinder GRAPHEME_SEGMENT_FINDER1 =
77             new SegmentFinder.PrescribedSegmentFinder(
78                     new int[] { 0, 1, 1, 2, 2, 4, 4, 5, 5, 7, 7, 8, 8, 9, 9, 10, 10, 11 });
79 
80     private static final SegmentFinder GRAPHEME_SEGMENT_FINDER2 =
81             new SegmentFinder.PrescribedSegmentFinder(
82                     new int[] { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10 });
83 
84     private static final SegmentFinder WORD_SEGMENT_FINDER1 =
85             new SegmentFinder.PrescribedSegmentFinder(new int[] { 0, 3, 4, 5, 5, 10 });
86     private static final SegmentFinder WORD_SEGMENT_FINDER2 =
87             new SegmentFinder.PrescribedSegmentFinder(new int[] { 0, 4, 5, 8, 9, 11 });
88 
89 
90     private static final SegmentFinder LINE_SEGMENT_FINDER1 =
91             new SegmentFinder.PrescribedSegmentFinder(new int[] { 0, 5, 5, 8, 8, 10, 10, 15 });
92     private static final SegmentFinder LINE_SEGMENT_FINDER2 =
93             new SegmentFinder.PrescribedSegmentFinder(new int[] { 0, 5, 5, 7, 7, 10, 10, 15 });
94 
95     @Test
96     @ApiTest(
97             apis = {
98                     "android.view.inputmethod.TextBoundsInfo#getStart",
99                     "android.view.inputmethod.TextBoundsInfo#getEnd",
100                     "android.view.inputmethod.TextBoundsInfo#getMatrix",
101                     "android.view.inputmethod.TextBoundsInfo#getCharacterBounds",
102                     "android.view.inputmethod.TextBoundsInfo#getCharacterFlags",
103                     "android.view.inputmethod.TextBoundsInfo#getCharacterBidiLevel",
104                     "android.view.inputmethod.TextBoundsInfo#getGraphemeSegmentFinder",
105                     "android.view.inputmethod.TextBoundsInfo#getWordSegmentFinder",
106                     "android.view.inputmethod.TextBoundsInfo#getLineSegmentFinder",
107                     "android.view.inputmethod.TextBoundsInfo.Builder#setStart",
108                     "android.view.inputmethod.TextBoundsInfo.Builder#setEnd",
109                     "android.view.inputmethod.TextBoundsInfo.Builder#setMatrix",
110                     "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterBounds",
111                     "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterFlags",
112                     "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterBidiLevel",
113                     "android.view.inputmethod.TextBoundsInfo.Builder#setGraphemeSegmentFinder",
114                     "android.view.inputmethod.TextBoundsInfo.Builder#setWordSegmentFinder",
115                     "android.view.inputmethod.TextBoundsInfo.Builder#setLineSegmentFinder",
116                     "android.view.inputmethod.TextBoundsInfo.Builder#build",
117             }
118     )
testBuilder()119     public void testBuilder() {
120         final int start1 = 5;
121         final int end1 = 10;
122         final Matrix matrix1 = new Matrix();
123         matrix1.setRotate(10f);
124 
125         TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(start1, end1);
126         TextBoundsInfo textBoundsInfo1 = builder.setMatrix(matrix1)
127                 .setCharacterBounds(CHARACTER_BOUNDS1)
128                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
129                 .setCharacterFlags(CHARACTER_FLAGS1)
130                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
131                 .setWordSegmentFinder(WORD_SEGMENT_FINDER1)
132                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
133                 .build();
134 
135         assertEquals(start1, textBoundsInfo1.getStartIndex());
136         assertEquals(end1, textBoundsInfo1.getEndIndex());
137         final Matrix actualMatrix = new Matrix();
138         textBoundsInfo1.getMatrix(actualMatrix);
139         assertEquals(matrix1, actualMatrix);
140         assertCharacterBounds(CHARACTER_BOUNDS1, textBoundsInfo1);
141         assertCharacterBidiLevel(CHARACTER_BIDI_LEVEL1, textBoundsInfo1);
142         assertCharacterFlags(CHARACTER_FLAGS1, textBoundsInfo1);
143         assertEquals(GRAPHEME_SEGMENT_FINDER1, textBoundsInfo1.getGraphemeSegmentFinder());
144         assertEquals(WORD_SEGMENT_FINDER1, textBoundsInfo1.getWordSegmentFinder());
145         assertEquals(LINE_SEGMENT_FINDER1, textBoundsInfo1.getLineSegmentFinder());
146 
147         // Build another TextBoundsInfo, making sure the Builder creates an identical object.
148         TextBoundsInfo textBoundsInfo2 = builder.build();
149         assertEquals(start1, textBoundsInfo2.getStartIndex());
150         assertEquals(end1, textBoundsInfo2.getEndIndex());
151         textBoundsInfo2.getMatrix(actualMatrix);
152         assertEquals(matrix1, actualMatrix);
153         assertCharacterBounds(CHARACTER_BOUNDS1, textBoundsInfo2);
154         assertCharacterBidiLevel(CHARACTER_BIDI_LEVEL1, textBoundsInfo2);
155         assertCharacterFlags(CHARACTER_FLAGS1, textBoundsInfo2);
156         assertEquals(GRAPHEME_SEGMENT_FINDER1, textBoundsInfo2.getGraphemeSegmentFinder());
157         assertEquals(WORD_SEGMENT_FINDER1, textBoundsInfo2.getWordSegmentFinder());
158         assertEquals(LINE_SEGMENT_FINDER1, textBoundsInfo2.getLineSegmentFinder());
159 
160         final int start2 = 8;
161         final int end2 = 13;
162         final Matrix matrix2 = new Matrix();
163         matrix2.setTranslate(20f, 30f);
164 
165         // Clear the existing parameters and build a different object.
166         TextBoundsInfo textBoundsInfo3 = builder.clear()
167                 .setStartAndEnd(start2, end2)
168                 .setMatrix(matrix2)
169                 .setCharacterBounds(CHARACTER_BOUNDS2)
170                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL2)
171                 .setCharacterFlags(CHARACTER_FLAGS2)
172                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER2)
173                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
174                 .setLineSegmentFinder(LINE_SEGMENT_FINDER2)
175                 .build();
176 
177         assertEquals(start2, textBoundsInfo3.getStartIndex());
178         assertEquals(end2, textBoundsInfo3.getEndIndex());
179         textBoundsInfo3.getMatrix(actualMatrix);
180         assertEquals(matrix2, actualMatrix);
181         assertCharacterBounds(CHARACTER_BOUNDS2, textBoundsInfo3);
182         assertCharacterBidiLevel(CHARACTER_BIDI_LEVEL2, textBoundsInfo3);
183         assertCharacterFlags(CHARACTER_FLAGS2, textBoundsInfo3);
184         assertEquals(GRAPHEME_SEGMENT_FINDER2, textBoundsInfo3.getGraphemeSegmentFinder());
185         assertEquals(WORD_SEGMENT_FINDER2, textBoundsInfo3.getWordSegmentFinder());
186         assertEquals(LINE_SEGMENT_FINDER2, textBoundsInfo3.getLineSegmentFinder());
187     }
188 
189     @Test
190     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo#writeToParcel" })
testTextBoundsInfo_writeToParcel()191     public void testTextBoundsInfo_writeToParcel() {
192         final Matrix matrix = new Matrix();
193         matrix.setRotate(15f);
194 
195         TextBoundsInfo textBoundsInfo1 = new TextBoundsInfo.Builder(5, 10)
196                 .setMatrix(matrix)
197                 .setCharacterBounds(CHARACTER_BOUNDS1)
198                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
199                 .setCharacterFlags(CHARACTER_FLAGS1)
200                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
201                 .setWordSegmentFinder(WORD_SEGMENT_FINDER1)
202                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
203                 .build();
204 
205         // Gut check, making sure the first TextBoundsInfo is built correctly.
206         assertEquals(5, textBoundsInfo1.getStartIndex());
207         assertEquals(10, textBoundsInfo1.getEndIndex());
208         final Matrix actualMatrix = new Matrix();
209         textBoundsInfo1.getMatrix(actualMatrix);
210         assertEquals(matrix, actualMatrix);
211         assertCharacterBounds(CHARACTER_BOUNDS1, textBoundsInfo1);
212         assertCharacterFlags(CHARACTER_FLAGS1, textBoundsInfo1);
213         assertCharacterBidiLevel(CHARACTER_BIDI_LEVEL1, textBoundsInfo1);
214         assertEquals(GRAPHEME_SEGMENT_FINDER1, textBoundsInfo1.getGraphemeSegmentFinder());
215         assertEquals(WORD_SEGMENT_FINDER1, textBoundsInfo1.getWordSegmentFinder());
216         assertEquals(LINE_SEGMENT_FINDER1, textBoundsInfo1.getLineSegmentFinder());
217 
218         Parcel parcel = Parcel.obtain();
219         final int dataPosition = parcel.dataPosition();
220         parcel.writeParcelable(textBoundsInfo1, 0);
221         parcel.setDataPosition(dataPosition);
222 
223         TextBoundsInfo textBoundsInfo2 =
224                 parcel.readParcelable(TextBoundsInfo.class.getClassLoader(), TextBoundsInfo.class);
225 
226         assertEquals(5, textBoundsInfo2.getStartIndex());
227         assertEquals(10, textBoundsInfo2.getEndIndex());
228         textBoundsInfo2.getMatrix(actualMatrix);
229         assertEquals(matrix, actualMatrix);
230         assertCharacterBounds(CHARACTER_BOUNDS1, textBoundsInfo2);
231         assertCharacterFlags(CHARACTER_FLAGS1, textBoundsInfo1);
232         assertCharacterBidiLevel(CHARACTER_BIDI_LEVEL1, textBoundsInfo1);
233         // Only check the iterator in the given range.
234         assertSegmentFinderEqualsInRange(GRAPHEME_SEGMENT_FINDER1,
235                 textBoundsInfo2.getGraphemeSegmentFinder(), 5, 10);
236         assertSegmentFinderEqualsInRange(WORD_SEGMENT_FINDER1,
237                 textBoundsInfo2.getWordSegmentFinder(), 5, 10);
238         assertSegmentFinderEqualsInRange(LINE_SEGMENT_FINDER1,
239                 textBoundsInfo2.getLineSegmentFinder(), 5, 10);
240     }
241 
242 
243     @Test(expected = IllegalStateException.class)
244     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_matrix_isRequired()245     public void testBuilder_matrix_isRequired() {
246         new TextBoundsInfo.Builder(5, 10)
247                 .setCharacterBounds(CHARACTER_BOUNDS1)
248                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
249                 .setCharacterFlags(CHARACTER_FLAGS1)
250                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
251                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
252                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
253                 .build();
254     }
255 
256     @Test(expected = IllegalStateException.class)
257     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_startAndEnd_isRequired()258     public void testBuilder_startAndEnd_isRequired() {
259         final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(5, 10);
260         builder.clear();
261         builder.setMatrix(Matrix.IDENTITY_MATRIX)
262                 .setCharacterBounds(CHARACTER_BOUNDS1)
263                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
264                 .setCharacterFlags(CHARACTER_FLAGS1)
265                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
266                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
267                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
268                 .build();
269     }
270 
271     @Test(expected = IllegalArgumentException.class)
272     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setStart" })
testBuilder_start_isNegative()273     public void testBuilder_start_isNegative() {
274         new TextBoundsInfo.Builder(-1 , 5);
275     }
276 
277     @Test(expected = IllegalArgumentException.class)
278     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setEnd" })
testBuilder_end_isNegative()279     public void testBuilder_end_isNegative() {
280         new TextBoundsInfo.Builder(0, -1);
281     }
282 
283     @Test(expected = IllegalArgumentException.class)
284     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_startGreaterThanEnd()285     public void testBuilder_startGreaterThanEnd() {
286         new TextBoundsInfo.Builder(1, 0);
287     }
288 
289     @Test(expected = IllegalStateException.class)
290     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_characterBounds_isRequired()291     public void testBuilder_characterBounds_isRequired() {
292         new TextBoundsInfo.Builder(5, 10)
293                 .setMatrix(Matrix.IDENTITY_MATRIX)
294                 .setCharacterFlags(CHARACTER_FLAGS1)
295                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
296                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
297                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
298                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
299                 .build();
300     }
301 
302     @Test(expected = NullPointerException.class)
303     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterBounds" })
testBuilder_characterBounds_isNull()304     public void testBuilder_characterBounds_isNull() {
305         new TextBoundsInfo.Builder(0, 5).setCharacterBounds(null);
306     }
307 
308     @Test(expected = IllegalStateException.class)
309     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_characterBounds_wrongLength()310     public void testBuilder_characterBounds_wrongLength() {
311         // Expected characterBounds.length == 20 to match the given range [0, 5).
312         new TextBoundsInfo.Builder(5, 10)
313                 .setMatrix(Matrix.IDENTITY_MATRIX)
314                 .setCharacterBounds(new float[16])
315                 .setCharacterFlags(CHARACTER_FLAGS1)
316                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
317                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
318                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
319                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
320                 .build();
321     }
322 
323     @Test(expected = IllegalStateException.class)
324     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_characterFlags_isRequired()325     public void testBuilder_characterFlags_isRequired() {
326         new TextBoundsInfo.Builder(5, 10)
327                 .setMatrix(Matrix.IDENTITY_MATRIX)
328                 .setCharacterBounds(CHARACTER_BOUNDS1)
329                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
330                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
331                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
332                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
333                 .build();
334     }
335 
336     @Test(expected = NullPointerException.class)
337     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterFlags" })
testBuilder_characterFlags_isNull()338     public void testBuilder_characterFlags_isNull() {
339         new TextBoundsInfo.Builder(5, 10).setCharacterFlags(null);
340     }
341 
342     @Test(expected = IllegalStateException.class)
343     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_characterFlags_wrongLength()344     public void testBuilder_characterFlags_wrongLength() {
345         // Expected characterFlags.length == 5 to match the given range [0, 5).
346         new TextBoundsInfo.Builder(5, 10)
347                 .setMatrix(Matrix.IDENTITY_MATRIX)
348                 .setCharacterFlags(new int[6])
349                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
350                 .setCharacterBounds(CHARACTER_BOUNDS1)
351                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
352                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
353                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
354                 .build();
355     }
356 
357     @Test(expected = IllegalStateException.class)
358     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_characterBidiLevel_isRequired()359     public void testBuilder_characterBidiLevel_isRequired() {
360         new TextBoundsInfo.Builder(5, 10)
361                 .setMatrix(Matrix.IDENTITY_MATRIX)
362                 .setCharacterBounds(CHARACTER_BOUNDS1)
363                 .setCharacterFlags(CHARACTER_FLAGS1)
364                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
365                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
366                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
367                 .build();
368     }
369 
370     @Test(expected = NullPointerException.class)
371     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterBidiLevel" })
testBuilder_characterBidiLevel_isNull()372     public void testBuilder_characterBidiLevel_isNull() {
373         new TextBoundsInfo.Builder(5, 10).setCharacterBidiLevel(null);
374     }
375 
376     @Test(expected = IllegalStateException.class)
377     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_characterBidiLevel_wrongLength()378     public void testBuilder_characterBidiLevel_wrongLength() {
379         // Expected characterBidiLevel.length == 5 to match the given range [0, 5).
380         new TextBoundsInfo.Builder(5, 10)
381                 .setMatrix(Matrix.IDENTITY_MATRIX)
382                 .setCharacterFlags(CHARACTER_FLAGS1)
383                 .setCharacterBidiLevel(new int[6])
384                 .setCharacterBounds(CHARACTER_BOUNDS1)
385                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
386                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
387                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
388                 .build();
389     }
390 
391     @Test
392     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterBidiLevel" })
testBuilder_characterBidiLevel_invalidBidiLevel()393     public void testBuilder_characterBidiLevel_invalidBidiLevel() {
394         // Bidi level must be in the range of [0, 125].
395         try {
396             new TextBoundsInfo.Builder(0, 1).setCharacterBidiLevel(new int[] { 126 });
397             fail();
398         } catch (IllegalArgumentException ignored) { }
399 
400         try {
401             new TextBoundsInfo.Builder(0 , 1).setCharacterBidiLevel(new int[] { -1 });
402             fail();
403         } catch (IllegalArgumentException ignored) { }
404     }
405 
406     @Test
407     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterBidiLevel" })
testBuilder_characterBidiLevel_isCleared()408     public void testBuilder_characterBidiLevel_isCleared() {
409         final var builder = new TextBoundsInfo.Builder(5, 10)
410                 .setMatrix(Matrix.IDENTITY_MATRIX)
411                 .setCharacterFlags(CHARACTER_FLAGS1)
412                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
413                 .setCharacterBounds(CHARACTER_BOUNDS1)
414                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
415                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
416                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1);
417         builder.build();
418 
419         builder.clear();
420         builder.setMatrix(Matrix.IDENTITY_MATRIX)
421                 .setStartAndEnd(5, 10)
422                 .setCharacterFlags(CHARACTER_FLAGS1)
423                 // omit setting characterBidiLevel.
424                 .setCharacterBounds(CHARACTER_BOUNDS1)
425                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
426                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
427                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1);
428 
429         assertThrows(IllegalStateException.class, builder::build);
430     }
431 
432     @Test(expected = IllegalArgumentException.class)
433     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setCharacterFlags" })
testBuilder_characterFlags_invalidFlag()434     public void testBuilder_characterFlags_invalidFlag() {
435         TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 5);
436 
437         // 1 << 20 is an unknown flags
438         int[] characterFlags = new int[] { 0, 1 << 20, 0, 0, 0};
439         builder.setCharacterFlags(characterFlags);
440     }
441 
442     @Test(expected = IllegalStateException.class)
443     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_graphemeSegmentFinder_isRequired()444     public void testBuilder_graphemeSegmentFinder_isRequired() {
445         new TextBoundsInfo.Builder(5, 10)
446                 .setMatrix(Matrix.IDENTITY_MATRIX)
447                 .setCharacterBounds(CHARACTER_BOUNDS1)
448                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
449                 .setCharacterFlags(CHARACTER_FLAGS1)
450                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
451                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
452                 .build();
453     }
454 
455     @Test(expected = NullPointerException.class)
456     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setGraphemeSegmentFinder" })
testBuilder_graphemeSegmentFinder_isNull()457     public void testBuilder_graphemeSegmentFinder_isNull() {
458         new TextBoundsInfo.Builder(5, 10).setGraphemeSegmentFinder(null);
459     }
460 
461     @Test(expected = IllegalStateException.class)
462     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_wordSegmentFinder_isRequired()463     public void testBuilder_wordSegmentFinder_isRequired() {
464         new TextBoundsInfo.Builder(5, 10)
465                 .setMatrix(Matrix.IDENTITY_MATRIX)
466                 .setCharacterBounds(CHARACTER_BOUNDS1)
467                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
468                 .setCharacterFlags(CHARACTER_FLAGS1)
469                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
470                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
471                 .build();
472     }
473 
474     @Test(expected = NullPointerException.class)
475     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setWordSegmentFinder" })
testBuilder_wordSegmentFinder_isNull()476     public void testBuilder_wordSegmentFinder_isNull() {
477         new TextBoundsInfo.Builder(5, 10).setWordSegmentFinder(null);
478     }
479 
480     @Test(expected = IllegalStateException.class)
481     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#build" })
testBuilder_lineSegmentFinder_isRequired()482     public void testBuilder_lineSegmentFinder_isRequired() {
483         new TextBoundsInfo.Builder(5, 10)
484                 .setMatrix(Matrix.IDENTITY_MATRIX)
485                 .setCharacterBounds(CHARACTER_BOUNDS1)
486                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
487                 .setCharacterFlags(CHARACTER_FLAGS1)
488                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
489                 .setWordSegmentFinder(WORD_SEGMENT_FINDER2)
490                 .build();
491     }
492 
493     @Test(expected = NullPointerException.class)
494     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfo.Builder#setLineSegmentFinder" })
testBuilder_lineSegmentFinder_isNull()495     public void testBuilder_lineSegmentFinder_isNull() {
496         new TextBoundsInfo.Builder(5, 10).setLineSegmentFinder(null);
497     }
498 
499     @Test
500     @ApiTest(apis = { "android.view.inputmethod.TextBoundsInfoResult#getResultCode",
501             "android.view.inputmethod.TextBoundsInfoResult#getTextBoundsInfo"})
testTextBoundsInfoResult_constructor()502     public void testTextBoundsInfoResult_constructor() {
503         TextBoundsInfoResult result =
504                 new TextBoundsInfoResult(TextBoundsInfoResult.CODE_UNSUPPORTED);
505         assertEquals(result.getResultCode(), TextBoundsInfoResult.CODE_UNSUPPORTED);
506 
507         result = new TextBoundsInfoResult(TextBoundsInfoResult.CODE_FAILED);
508         assertEquals(result.getResultCode(), TextBoundsInfoResult.CODE_FAILED);
509 
510         result = new TextBoundsInfoResult(TextBoundsInfoResult.CODE_CANCELLED);
511         assertEquals(result.getResultCode(), TextBoundsInfoResult.CODE_CANCELLED);
512 
513         final TextBoundsInfo textBoundsInfo = new TextBoundsInfo.Builder(5, 10)
514                 .setCharacterBounds(CHARACTER_BOUNDS1)
515                 .setCharacterBidiLevel(CHARACTER_BIDI_LEVEL1)
516                 .setMatrix(Matrix.IDENTITY_MATRIX)
517                 .setCharacterFlags(CHARACTER_FLAGS1)
518                 .setGraphemeSegmentFinder(GRAPHEME_SEGMENT_FINDER1)
519                 .setWordSegmentFinder(WORD_SEGMENT_FINDER1)
520                 .setLineSegmentFinder(LINE_SEGMENT_FINDER1)
521                 .build();
522 
523         result = new TextBoundsInfoResult(TextBoundsInfoResult.CODE_SUCCESS,
524                         textBoundsInfo);
525         assertEquals(result.getResultCode(), TextBoundsInfoResult.CODE_SUCCESS);
526         assertEquals(result.getTextBoundsInfo(), textBoundsInfo);
527     }
528 
529     @Test
testTextBoundsInfoResult_resultCodeIsSuccess_textBoundsInfoIsRequired()530     public void testTextBoundsInfoResult_resultCodeIsSuccess_textBoundsInfoIsRequired() {
531         try {
532             new TextBoundsInfoResult(TextBoundsInfoResult.CODE_SUCCESS);
533             fail();
534         } catch (IllegalStateException ignored) { }
535 
536         try {
537             new TextBoundsInfoResult(TextBoundsInfoResult.CODE_SUCCESS, null);
538             fail();
539         } catch (IllegalStateException ignored) { }
540 
541     }
542 
assertCharacterBounds(float[] characterBounds, TextBoundsInfo textBoundsInfo)543     private static void assertCharacterBounds(float[] characterBounds,
544             TextBoundsInfo textBoundsInfo) {
545         final int start = textBoundsInfo.getStartIndex();
546         final int end = textBoundsInfo.getEndIndex();
547         for (int offset = 0; offset < end - start; ++offset) {
548             final RectF expectedRect = new RectF(
549                     characterBounds[4 * offset],
550                     characterBounds[4 * offset + 1],
551                     characterBounds[4 * offset + 2],
552                     characterBounds[4 * offset + 3]);
553 
554             final RectF actualRectF = new RectF();
555             textBoundsInfo.getCharacterBounds(offset + start, actualRectF);
556             assertEquals(expectedRect, actualRectF);
557         }
558     }
559 
assertCharacterFlags(int[] characterFlags, TextBoundsInfo textBoundsInfo)560     private static void assertCharacterFlags(int[] characterFlags,
561             TextBoundsInfo textBoundsInfo) {
562         final int start = textBoundsInfo.getStartIndex();
563         final int end = textBoundsInfo.getEndIndex();
564         for (int offset = 0; offset < end - start; ++offset) {
565             assertEquals(characterFlags[offset],
566                     textBoundsInfo.getCharacterFlags(offset + start));
567         }
568     }
569 
assertCharacterBidiLevel(int[] characterBidiLevels, TextBoundsInfo textBoundsInfo)570     private static void assertCharacterBidiLevel(int[] characterBidiLevels,
571             TextBoundsInfo textBoundsInfo) {
572         final int start = textBoundsInfo.getStartIndex();
573         final int end = textBoundsInfo.getEndIndex();
574         for (int offset = 0; offset < end - start; ++offset) {
575             assertEquals(characterBidiLevels[offset],
576                     textBoundsInfo.getCharacterBidiLevel(offset + start));
577         }
578     }
579 
580     /**
581      * Helper method to assert that the given {@link SegmentFinder}s are the same in the given
582      * range.
583      */
assertSegmentFinderEqualsInRange(SegmentFinder expect, SegmentFinder actual, int start, int end)584     private static void assertSegmentFinderEqualsInRange(SegmentFinder expect,
585             SegmentFinder actual, int start, int end) {
586         int expectSegmentEnd = expect.nextEndBoundary(start);
587         int expectSegmentStart = expect.previousStartBoundary(expectSegmentEnd);
588 
589         int actualSegmentEnd = actual.nextEndBoundary(start);
590         int actualSegmentStart = actual.previousStartBoundary(actualSegmentEnd);
591 
592         while (expectSegmentStart != SegmentFinder.DONE && expectSegmentEnd <= end) {
593             assertEquals(expectSegmentStart, actualSegmentStart);
594             assertEquals(expectSegmentEnd, actualSegmentEnd);
595 
596             expectSegmentStart = expect.nextStartBoundary(expectSegmentStart);
597             actualSegmentStart = actual.nextStartBoundary(actualSegmentStart);
598 
599             expectSegmentEnd = expect.nextEndBoundary(expectSegmentEnd);
600             actualSegmentEnd = actual.nextEndBoundary(actualSegmentEnd);
601         }
602 
603         // The actual SegmentFinder doesn't have more segments within the range.
604         assertTrue(actualSegmentEnd > end || actualSegmentEnd == SegmentFinder.DONE);
605     }
606 }
607