CursorAnchorInfoTest.java revision eea0b8b051e916051d0d09da3f41f9ec4d508bff
1/*
2 * Copyright (C) 2014 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
17package android.os;
18
19import android.graphics.Matrix;
20import android.graphics.RectF;
21import android.test.InstrumentationTestCase;
22import android.test.suitebuilder.annotation.SmallTest;
23import android.text.TextUtils;
24import android.view.inputmethod.CursorAnchorInfo;
25import android.view.inputmethod.CursorAnchorInfo.Builder;
26
27import java.util.Objects;
28
29public class CursorAnchorInfoTest extends InstrumentationTestCase {
30    // null represents a character that is invisible, for example because it's overlapped by some
31    // other UI elements.
32    private static final RectF[] MANY_RECTS = new RectF[] {
33            null,
34            new RectF(102.0f, 202.0f, 302.0f, 402.0f),
35            new RectF(103.0f, 203.0f, 303.0f, 403.0f),
36            new RectF(104.0f, 204.0f, 304.0f, 404.0f),
37            new RectF(105.0f, 205.0f, 305.0f, 405.0f),
38            new RectF(106.0f, 206.0f, 306.0f, 406.0f),
39            null,
40            new RectF(108.0f, 208.0f, 308.0f, 408.0f),
41            new RectF(109.0f, 209.0f, 309.0f, 409.0f),
42            new RectF(110.0f, 210.0f, 310.0f, 410.0f),
43            new RectF(111.0f, 211.0f, 311.0f, 411.0f),
44            new RectF(112.0f, 212.0f, 312.0f, 412.0f),
45            new RectF(113.0f, 213.0f, 313.0f, 413.0f),
46            new RectF(114.0f, 214.0f, 314.0f, 414.0f),
47            new RectF(115.0f, 215.0f, 315.0f, 415.0f),
48            new RectF(116.0f, 216.0f, 316.0f, 416.0f),
49            new RectF(117.0f, 217.0f, 317.0f, 417.0f),
50            null,
51            null,
52    };
53
54    @SmallTest
55    public void testBuilder() throws Exception {
56        final int SELECTION_START = 30;
57        final int SELECTION_END = 40;
58        final int COMPOSING_TEXT_START = 32;
59        final String COMPOSING_TEXT = "test";
60        final float INSERTION_MARKER_HORIZONTAL = 10.5f;
61        final float INSERTION_MARKER_TOP = 100.1f;
62        final float INSERTION_MARKER_BASELINE = 110.4f;
63        final float INSERTION_MARKER_BOTOM = 111.0f;
64        Matrix TRANSFORM_MATRIX = new Matrix(Matrix.IDENTITY_MATRIX);
65        TRANSFORM_MATRIX.setScale(10.0f, 20.0f);
66
67        final Builder builder = new Builder();
68        builder.setSelectionRange(SELECTION_START, SELECTION_END)
69                .setComposingText(COMPOSING_TEXT_START, COMPOSING_TEXT)
70                .setInsertionMarkerLocation(INSERTION_MARKER_HORIZONTAL, INSERTION_MARKER_TOP,
71                        INSERTION_MARKER_BASELINE, INSERTION_MARKER_BOTOM)
72                .setMatrix(TRANSFORM_MATRIX);
73        for (int i = 0; i < MANY_RECTS.length; i++) {
74            final RectF rect = MANY_RECTS[i];
75            if (rect != null) {
76                builder.addCharacterRect(i, rect.left, rect.top, rect.right, rect.bottom);
77            }
78        }
79
80        final CursorAnchorInfo info = builder.build();
81        assertEquals(SELECTION_START, info.getSelectionStart());
82        assertEquals(SELECTION_END, info.getSelectionEnd());
83        assertEquals(COMPOSING_TEXT_START, info.getComposingTextStart());
84        assertTrue(TextUtils.equals(COMPOSING_TEXT, info.getComposingText()));
85        assertEquals(INSERTION_MARKER_HORIZONTAL, info.getInsertionMarkerHorizontal());
86        assertEquals(INSERTION_MARKER_TOP, info.getInsertionMarkerTop());
87        assertEquals(INSERTION_MARKER_BASELINE, info.getInsertionMarkerBaseline());
88        assertEquals(INSERTION_MARKER_BOTOM, info.getInsertionMarkerBottom());
89        assertEquals(TRANSFORM_MATRIX, info.getMatrix());
90        for (int i = 0; i < MANY_RECTS.length; i++) {
91            final RectF expectedRect = MANY_RECTS[i];
92            assertEquals(expectedRect, info.getCharacterRect(i));
93        }
94        assertNull(info.getCharacterRect(-1));
95        assertNull(info.getCharacterRect(MANY_RECTS.length + 1));
96
97        // Make sure that the builder can reproduce the same object.
98        final CursorAnchorInfo info2 = builder.build();
99        assertEquals(SELECTION_START, info2.getSelectionStart());
100        assertEquals(SELECTION_END, info2.getSelectionEnd());
101        assertEquals(COMPOSING_TEXT_START, info2.getComposingTextStart());
102        assertTrue(TextUtils.equals(COMPOSING_TEXT, info2.getComposingText()));
103        assertEquals(INSERTION_MARKER_HORIZONTAL, info2.getInsertionMarkerHorizontal());
104        assertEquals(INSERTION_MARKER_TOP, info2.getInsertionMarkerTop());
105        assertEquals(INSERTION_MARKER_BASELINE, info2.getInsertionMarkerBaseline());
106        assertEquals(INSERTION_MARKER_BOTOM, info2.getInsertionMarkerBottom());
107        assertEquals(TRANSFORM_MATRIX, info2.getMatrix());
108        for (int i = 0; i < MANY_RECTS.length; i++) {
109            final RectF expectedRect = MANY_RECTS[i];
110            assertEquals(expectedRect, info2.getCharacterRect(i));
111        }
112        assertNull(info2.getCharacterRect(-1));
113        assertNull(info2.getCharacterRect(MANY_RECTS.length + 1));
114        assertEquals(info, info2);
115        assertEquals(info.hashCode(), info2.hashCode());
116
117        // Make sure that object can be marshaled via {@link Parsel}.
118        final CursorAnchorInfo info3 = cloneViaParcel(info2);
119        assertEquals(SELECTION_START, info3.getSelectionStart());
120        assertEquals(SELECTION_END, info3.getSelectionEnd());
121        assertEquals(COMPOSING_TEXT_START, info3.getComposingTextStart());
122        assertTrue(TextUtils.equals(COMPOSING_TEXT, info3.getComposingText()));
123        assertEquals(INSERTION_MARKER_HORIZONTAL, info3.getInsertionMarkerHorizontal());
124        assertEquals(INSERTION_MARKER_TOP, info3.getInsertionMarkerTop());
125        assertEquals(INSERTION_MARKER_BASELINE, info3.getInsertionMarkerBaseline());
126        assertEquals(INSERTION_MARKER_BOTOM, info3.getInsertionMarkerBottom());
127        assertEquals(TRANSFORM_MATRIX, info3.getMatrix());
128        for (int i = 0; i < MANY_RECTS.length; i++) {
129            final RectF expectedRect = MANY_RECTS[i];
130            assertEquals(expectedRect, info3.getCharacterRect(i));
131        }
132        assertNull(info3.getCharacterRect(-1));
133        assertNull(info3.getCharacterRect(MANY_RECTS.length + 1));
134        assertEquals(info.hashCode(), info3.hashCode());
135
136        builder.reset();
137        final CursorAnchorInfo uninitializedInfo = builder.build();
138        assertEquals(-1, uninitializedInfo.getSelectionStart());
139        assertEquals(-1, uninitializedInfo.getSelectionEnd());
140        assertEquals(-1, uninitializedInfo.getComposingTextStart());
141        assertNull(uninitializedInfo.getComposingText());
142        assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerHorizontal());
143        assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerTop());
144        assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBaseline());
145        assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBottom());
146        assertEquals(Matrix.IDENTITY_MATRIX, uninitializedInfo.getMatrix());
147    }
148
149    private static void assertNotEquals(final CursorAnchorInfo reference,
150            final CursorAnchorInfo actual) {
151        assertFalse(Objects.equals(reference, actual));
152    }
153
154    @SmallTest
155    public void testEquality() throws Exception {
156        final Matrix MATRIX1 = new Matrix();
157        MATRIX1.setTranslate(10.0f, 20.0f);
158        final Matrix MATRIX2 = new Matrix();
159        MATRIX2.setTranslate(110.0f, 120.0f);
160        final Matrix NAN_MATRIX = new Matrix();
161        NAN_MATRIX.setValues(new float[]{
162                Float.NaN, Float.NaN, Float.NaN,
163                Float.NaN, Float.NaN, Float.NaN,
164                Float.NaN, Float.NaN, Float.NaN});
165        final int SELECTION_START1 = 2;
166        final int SELECTION_END1 = 7;
167        final String COMPOSING_TEXT1 = "0123456789";
168        final int COMPOSING_TEXT_START1 = 0;
169        final float INSERTION_MARKER_HORIZONTAL1 = 10.5f;
170        final float INSERTION_MARKER_TOP1 = 100.1f;
171        final float INSERTION_MARKER_BASELINE1 = 110.4f;
172        final float INSERTION_MARKER_BOTOM1 = 111.0f;
173        final int SELECTION_START2 = 4;
174        final int SELECTION_END2 = 8;
175        final String COMPOSING_TEXT2 = "9876543210";
176        final int COMPOSING_TEXT_START2 = 3;
177        final float INSERTION_MARKER_HORIZONTAL2 = 14.5f;
178        final float INSERTION_MARKER_TOP2 = 200.1f;
179        final float INSERTION_MARKER_BASELINE2 = 210.4f;
180        final float INSERTION_MARKER_BOTOM2 = 211.0f;
181
182        // Default instance should be equal.
183        assertEquals(new Builder().build(), new Builder().build());
184
185        assertEquals(
186                new Builder().setSelectionRange(SELECTION_START1, SELECTION_END1).build(),
187                new Builder().setSelectionRange(SELECTION_START1, SELECTION_END1).build());
188        assertNotEquals(
189                new Builder().setSelectionRange(SELECTION_START1, SELECTION_END1).build(),
190                new Builder().setSelectionRange(SELECTION_START1, SELECTION_END2).build());
191        assertNotEquals(
192                new Builder().setSelectionRange(SELECTION_START1, SELECTION_END1).build(),
193                new Builder().setSelectionRange(SELECTION_START2, SELECTION_END1).build());
194        assertNotEquals(
195                new Builder().setSelectionRange(SELECTION_START1, SELECTION_END1).build(),
196                new Builder().setSelectionRange(SELECTION_START2, SELECTION_END2).build());
197        assertEquals(
198                new Builder().setComposingText(COMPOSING_TEXT_START1, COMPOSING_TEXT1).build(),
199                new Builder().setComposingText(COMPOSING_TEXT_START1, COMPOSING_TEXT1).build());
200        assertNotEquals(
201                new Builder().setComposingText(COMPOSING_TEXT_START1, COMPOSING_TEXT1).build(),
202                new Builder().setComposingText(COMPOSING_TEXT_START2, COMPOSING_TEXT1).build());
203        assertNotEquals(
204                new Builder().setComposingText(COMPOSING_TEXT_START1, COMPOSING_TEXT1).build(),
205                new Builder().setComposingText(COMPOSING_TEXT_START1, COMPOSING_TEXT2).build());
206        assertNotEquals(
207                new Builder().setComposingText(COMPOSING_TEXT_START1, COMPOSING_TEXT1).build(),
208                new Builder().setComposingText(COMPOSING_TEXT_START2, COMPOSING_TEXT2).build());
209
210        // For insertion marker locations, {@link Float#NaN} is treated as if it was a number.
211        assertEquals(
212                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
213                        Float.NaN, Float.NaN, Float.NaN, Float.NaN).build(),
214                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
215                        Float.NaN, Float.NaN, Float.NaN, Float.NaN).build());
216
217        // Check Matrix.
218        assertEquals(
219                new Builder().setMatrix(MATRIX1).build(),
220                new Builder().setMatrix(MATRIX1).build());
221        assertNotEquals(
222                new Builder().setMatrix(MATRIX1).build(),
223                new Builder().setMatrix(MATRIX2).build());
224        assertNotEquals(
225                new Builder().setMatrix(MATRIX1).build(),
226                new Builder().setMatrix(NAN_MATRIX).build());
227        // Unlike insertion marker locations, {@link Float#NaN} in the matrix is treated as just a
228        // NaN as usual (NaN == NaN -> false).
229        assertNotEquals(
230                new Builder().setMatrix(NAN_MATRIX).build(),
231                new Builder().setMatrix(NAN_MATRIX).build());
232
233        assertEquals(
234                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
235                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
236                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build(),
237                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
238                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
239                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build());
240        assertNotEquals(
241                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
242                        Float.NaN, INSERTION_MARKER_TOP1,
243                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build(),
244                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
245                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
246                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build());
247        assertNotEquals(
248                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
249                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
250                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build(),
251                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
252                        INSERTION_MARKER_HORIZONTAL2, INSERTION_MARKER_TOP1,
253                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build());
254        assertNotEquals(
255                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
256                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
257                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build(),
258                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
259                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP2,
260                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build());
261        assertNotEquals(
262                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
263                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
264                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build(),
265                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
266                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
267                        INSERTION_MARKER_BASELINE2, INSERTION_MARKER_BOTOM1).build());
268        assertNotEquals(
269                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
270                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
271                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build(),
272                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
273                        INSERTION_MARKER_HORIZONTAL2, INSERTION_MARKER_TOP1,
274                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build());
275        assertNotEquals(
276                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
277                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
278                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM1).build(),
279                new Builder().setMatrix(MATRIX1).setInsertionMarkerLocation(
280                        INSERTION_MARKER_HORIZONTAL1, INSERTION_MARKER_TOP1,
281                        INSERTION_MARKER_BASELINE1, INSERTION_MARKER_BOTOM2).build());
282    }
283
284    @SmallTest
285    public void testMatrixIsCopied() throws Exception {
286        final Matrix MATRIX1 = new Matrix();
287        MATRIX1.setTranslate(10.0f, 20.0f);
288        final Matrix MATRIX2 = new Matrix();
289        MATRIX2.setTranslate(110.0f, 120.0f);
290        final Matrix MATRIX3 = new Matrix();
291        MATRIX3.setTranslate(210.0f, 220.0f);
292        final Matrix matrix = new Matrix();
293        final Builder builder = new Builder();
294
295        matrix.set(MATRIX1);
296        builder.setMatrix(matrix);
297        matrix.postRotate(90.0f);
298
299        final CursorAnchorInfo firstInstance = builder.build();
300        assertEquals(MATRIX1, firstInstance.getMatrix());
301        matrix.set(MATRIX2);
302        builder.setMatrix(matrix);
303        final CursorAnchorInfo secondInstance = builder.build();
304        assertEquals(MATRIX1, firstInstance.getMatrix());
305        assertEquals(MATRIX2, secondInstance.getMatrix());
306
307        matrix.set(MATRIX3);
308        assertEquals(MATRIX1, firstInstance.getMatrix());
309        assertEquals(MATRIX2, secondInstance.getMatrix());
310    }
311
312    @SmallTest
313    public void testMatrixIsRequired() throws Exception {
314        final int SELECTION_START = 30;
315        final int SELECTION_END = 40;
316        final int COMPOSING_TEXT_START = 32;
317        final String COMPOSING_TEXT = "test";
318        final float INSERTION_MARKER_HORIZONTAL = 10.5f;
319        final float INSERTION_MARKER_TOP = 100.1f;
320        final float INSERTION_MARKER_BASELINE = 110.4f;
321        final float INSERTION_MARKER_BOTOM = 111.0f;
322        Matrix TRANSFORM_MATRIX = new Matrix(Matrix.IDENTITY_MATRIX);
323        TRANSFORM_MATRIX.setScale(10.0f, 20.0f);
324
325        final Builder builder = new Builder();
326        // Check twice to make sure if Builder#reset() works as expected.
327        for (int repeatCount = 0; repeatCount < 2; ++repeatCount) {
328            builder.setSelectionRange(SELECTION_START, SELECTION_END)
329                    .setComposingText(COMPOSING_TEXT_START, COMPOSING_TEXT);
330            try {
331                // Should succeed as coordinate transformation matrix is not required if no
332                // positional information is specified.
333                new Builder().build();
334            } catch (IllegalArgumentException ex) {
335                assertTrue(false);
336            }
337
338            builder.setInsertionMarkerLocation(INSERTION_MARKER_HORIZONTAL, INSERTION_MARKER_TOP,
339                    INSERTION_MARKER_BASELINE, INSERTION_MARKER_BOTOM);
340            try {
341                // Coordinate transformation matrix is required if no positional information is
342                // specified.
343                new Builder().build();
344            } catch (IllegalArgumentException ex) {
345                assertTrue(true);
346            }
347
348            builder.setMatrix(TRANSFORM_MATRIX);
349            try {
350                // Should succeed as coordinate transformation matrix is required.
351                new Builder().build();
352            } catch (IllegalArgumentException ex) {
353                assertTrue(false);
354            }
355
356            builder.reset();
357        }
358    }
359
360    @SmallTest
361    public void testBuilderAdd() throws Exception {
362        // A negative index should be rejected.
363        try {
364            new Builder().addCharacterRect(-1, 0.0f, 0.0f, 0.0f, 0.0f);
365        } catch (IllegalArgumentException ex) {
366            assertTrue(true);
367        }
368    }
369
370    private static CursorAnchorInfo cloneViaParcel(final CursorAnchorInfo src) {
371        Parcel parcel = null;
372        try {
373            parcel = Parcel.obtain();
374            src.writeToParcel(parcel, 0);
375            parcel.setDataPosition(0);
376            return new CursorAnchorInfo(parcel);
377        } finally {
378            if (parcel != null) {
379                parcel.recycle();
380            }
381        }
382    }
383}
384