1 /*
2  * Copyright (C) 2009 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.assertFalse;
21 import static org.junit.Assert.assertNotEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 
27 import android.os.Bundle;
28 import android.os.LocaleList;
29 import android.os.Parcel;
30 import android.platform.test.annotations.AppModeSdkSandbox;
31 import android.platform.test.annotations.RequiresFlagsEnabled;
32 import android.platform.test.flag.junit.CheckFlagsRule;
33 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
34 import android.test.MoreAsserts;
35 import android.text.SpannableStringBuilder;
36 import android.text.TextUtils;
37 import android.util.StringBuilderPrinter;
38 import android.view.MotionEvent;
39 import android.view.inputmethod.DeleteGesture;
40 import android.view.inputmethod.EditorInfo;
41 import android.view.inputmethod.Flags;
42 import android.view.inputmethod.HandwritingGesture;
43 import android.view.inputmethod.InputConnection;
44 import android.view.inputmethod.InsertGesture;
45 import android.view.inputmethod.PreviewableHandwritingGesture;
46 import android.view.inputmethod.SelectGesture;
47 import android.view.inputmethod.SurroundingText;
48 
49 import androidx.test.filters.SmallTest;
50 import androidx.test.runner.AndroidJUnit4;
51 
52 import com.android.compatibility.common.util.ApiTest;
53 
54 import org.junit.Rule;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Set;
63 import java.util.stream.Collectors;
64 import java.util.stream.Stream;
65 
66 @SmallTest
67 @RunWith(AndroidJUnit4.class)
68 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
69 public class EditorInfoTest {
70 
71     @Rule
72     public final CheckFlagsRule mCheckFlagsRule =
73             DeviceFlagsValueProvider.createCheckFlagsRule();
74     private static final int TEST_TEXT_LENGTH = 2048;
75     /** A text with 1 million chars! This is way too long. */
76     private static final int OVER_SIZED_TEXT_LENGTH = 1 * 1024 * 1024;
77     /** To get the longest available text from getInitialText methods. */
78     private static final int REQUEST_LONGEST_AVAILABLE_TEXT = OVER_SIZED_TEXT_LENGTH; //
79 
80     @Test
81     @ApiTest(apis = {"android.view.inputmethod.EditorInfo#setSupportedHandwritingGestures",
82             "android.view.inputmethod.EditorInfo#setInitialToolType",
83             "android.view.inputmethod.EditorInfo#getSupportedHandwritingGestures",
84             "android.view.inputmethod.EditorInfo#getInitialToolType"})
testEditorInfo()85     public void testEditorInfo() {
86         EditorInfo info = new EditorInfo();
87         CharSequence testInitialText = createTestText(TEST_TEXT_LENGTH);
88 
89         info.actionId = 1;
90         info.actionLabel = "actionLabel";
91         info.fieldId = 2;
92         info.fieldName = "fieldName";
93         info.hintText = "hintText";
94         info.imeOptions = EditorInfo.IME_FLAG_NO_ENTER_ACTION;
95         info.initialCapsMode = TextUtils.CAP_MODE_CHARACTERS;
96         info.initialSelEnd = 10;
97         info.initialSelStart = 0;
98         info.inputType = EditorInfo.TYPE_MASK_CLASS;
99         info.label = "label";
100         info.packageName = "android.view.cts";
101         info.privateImeOptions = "privateIme";
102         Bundle b = new Bundle();
103         info.setInitialSurroundingText(testInitialText);
104         String key = "bundleKey";
105         String value = "bundleValue";
106         b.putString(key, value);
107         info.extras = b;
108         info.hintLocales = LocaleList.forLanguageTags("en-PH,en-US");
109         info.contentMimeTypes = new String[]{"image/gif", "image/png"};
110         info.setInitialToolType(MotionEvent.TOOL_TYPE_FINGER);
111         info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class));
112         info.setSupportedHandwritingGesturePreviews(
113                 Stream.of(SelectGesture.class).collect(Collectors.toSet()));
114 
115         assertEquals(0, info.describeContents());
116 
117         Parcel p = Parcel.obtain();
118         info.writeToParcel(p, 0);
119         p.setDataPosition(0);
120         EditorInfo targetInfo = EditorInfo.CREATOR.createFromParcel(p);
121         p.recycle();
122         assertEquals(info.actionId, targetInfo.actionId);
123         assertEquals(info.fieldId, targetInfo.fieldId);
124         assertEquals(info.fieldName, targetInfo.fieldName);
125         assertEquals(info.imeOptions, targetInfo.imeOptions);
126         assertEquals(info.initialCapsMode, targetInfo.initialCapsMode);
127         assertEquals(info.initialSelEnd, targetInfo.initialSelEnd);
128         assertEquals(info.initialSelStart, targetInfo.initialSelStart);
129         assertEquals(info.inputType, targetInfo.inputType);
130         assertEquals(info.packageName, targetInfo.packageName);
131         assertEquals(info.privateImeOptions, targetInfo.privateImeOptions);
132         assertTrue(TextUtils.equals(testInitialText, concatInitialSurroundingText(targetInfo)));
133         assertEquals(info.hintText.toString(), targetInfo.hintText.toString());
134         assertEquals(info.actionLabel.toString(), targetInfo.actionLabel.toString());
135         assertEquals(info.label.toString(), targetInfo.label.toString());
136         assertEquals(info.extras.getString(key), targetInfo.extras.getString(key));
137         assertEquals(info.hintLocales, targetInfo.hintLocales);
138         assertEquals(info.getInitialToolType(), targetInfo.getInitialToolType());
139         assertEquals(info.getSupportedHandwritingGestures(),
140                 targetInfo.getSupportedHandwritingGestures());
141         MoreAsserts.assertEquals(info.contentMimeTypes, targetInfo.contentMimeTypes);
142 
143         StringBuilder sb = new StringBuilder();
144         StringBuilderPrinter sbPrinter = new StringBuilderPrinter(sb);
145         String prefix = "TestEditorInfo";
146         info.dump(sbPrinter, prefix);
147 
148         assertFalse(TextUtils.isEmpty(sb.toString()));
149         assertFalse(sb.toString().contains(testInitialText));
150     }
151 
152     @ApiTest(apis = {"android.view.inputmethod.EditorInfo#setSupportedHandwritingGestures",
153             "android.view.inputmethod.EditorInfo#getSupportedHandwritingGestures"})
154     @Test
testSupportedHandwritingGestures()155     public void testSupportedHandwritingGestures() {
156         EditorInfo info = new EditorInfo();
157         info.setSupportedHandwritingGestures(new ArrayList<>());
158         assertTrue(info.getSupportedHandwritingGestures().isEmpty());
159 
160         info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class));
161         assertEquals(info.getSupportedHandwritingGestures().get(0), SelectGesture.class);
162 
163         info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class, InsertGesture.class,
164                 DeleteGesture.class));
165         List<Class<? extends HandwritingGesture>> gestures = info.getSupportedHandwritingGestures();
166         assertEquals(gestures.size(), 3);
167         assertTrue(gestures.contains(SelectGesture.class));
168         assertTrue(gestures.contains(DeleteGesture.class));
169         assertTrue(gestures.contains(InsertGesture.class));
170     }
171 
172     @ApiTest(apis = {"android.view.inputmethod.EditorInfo#setSupportedHandwritingGesturePreviews",
173             "android.view.inputmethod.EditorInfo#getSupportedHandwritingGesturePreviews",
174             "android.view.inputmethod.EditorInfo#getSupportedHandwritingGestures"})
175     @Test
testSupportedHandwritingGesturePreviews()176     public void testSupportedHandwritingGesturePreviews() {
177         EditorInfo info = new EditorInfo();
178         info.setSupportedHandwritingGesturePreviews(new HashSet<>());
179         assertTrue(info.getSupportedHandwritingGesturePreviews().isEmpty());
180 
181         Set<Class<? extends PreviewableHandwritingGesture>> selectGestureSet =
182                 Stream.of(SelectGesture.class).collect(Collectors.toSet());
183         info.setSupportedHandwritingGesturePreviews(selectGestureSet);
184         assertEquals(info.getSupportedHandwritingGesturePreviews(), selectGestureSet);
185 
186         assertNotEquals(info.getSupportedHandwritingGesturePreviews(),
187                 info.getSupportedHandwritingGestures());
188 
189         info.setSupportedHandwritingGesturePreviews(
190                 Stream.of(SelectGesture.class, DeleteGesture.class).collect(Collectors.toSet()));
191         Set<Class<? extends PreviewableHandwritingGesture>> gestures =
192                 info.getSupportedHandwritingGesturePreviews();
193         assertEquals(gestures.size(), 2);
194         assertTrue(gestures.contains(SelectGesture.class));
195         assertTrue(gestures.contains(DeleteGesture.class));
196     }
197 
198     /**
199      * Test {@link EditorInfo#isStylusHandwritingEnabled()}.
200      */
201     @ApiTest(apis = {"android.view.inputmethod.EditorInfo#setStylusHandwritingEnabled",
202             "android.view.inputmethod.EditorInfo#isStylusHandwritingEnabled"})
203     @Test
204     @RequiresFlagsEnabled(Flags.FLAG_EDITORINFO_HANDWRITING_ENABLED)
testStylusHandwritingEnabled()205     public void testStylusHandwritingEnabled() {
206         EditorInfo info = new EditorInfo();
207         info.setStylusHandwritingEnabled(true);
208         assertTrue(info.isStylusHandwritingEnabled());
209         Parcel p = Parcel.obtain();
210         info.writeToParcel(p, 0 /* flags */);
211         p.setDataPosition(0);
212         EditorInfo targetInfo = EditorInfo.CREATOR.createFromParcel(p);
213         p.recycle();
214         assertEquals(info.isStylusHandwritingEnabled(), targetInfo.isStylusHandwritingEnabled());
215     }
216 
217     @Test
testNullHintLocals()218     public void testNullHintLocals() {
219         EditorInfo info = new EditorInfo();
220         info.hintLocales = null;
221         Parcel p = Parcel.obtain();
222         info.writeToParcel(p, 0);
223         p.setDataPosition(0);
224         EditorInfo targetInfo = EditorInfo.CREATOR.createFromParcel(p);
225         p.recycle();
226         assertNull(targetInfo.hintLocales);
227     }
228 
229     @Test
testInitialSurroundingText_nullInput_throwsException()230     public void testInitialSurroundingText_nullInput_throwsException() {
231         final EditorInfo info = new EditorInfo();
232 
233         try {
234             info.setInitialSurroundingText(null);
235             fail("Shall not take null input");
236         } catch (NullPointerException expected) {
237             // Expected behavior, nothing to do.
238         }
239     }
240 
241     @Test
testInitialSurroundingText_passwordTypes_notObtain()242     public void testInitialSurroundingText_passwordTypes_notObtain() {
243         final EditorInfo info = new EditorInfo();
244         final CharSequence testInitialText = createTestText(/* size= */ 10);
245         info.initialSelStart = 1;
246         info.initialSelEnd = 2;
247 
248         // Text password type
249         info.inputType = (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
250 
251         info.setInitialSurroundingText(testInitialText);
252 
253         assertExpectedTextLength(info,
254                 /* expectBeforeCursorLength= */null,
255                 /* expectSelectionLength= */null,
256                 /* expectAfterCursorLength= */null,
257                 /* expectSurroundingText= */null);
258 
259         // Web password type
260         info.inputType = (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
261 
262         info.setInitialSurroundingText(testInitialText);
263 
264         assertExpectedTextLength(info,
265                 /* expectBeforeCursorLength= */null,
266                 /* expectSelectionLength= */null,
267                 /* expectAfterCursorLength= */null,
268                 /* expectSurroundingText= */null);
269 
270         // Number password type
271         info.inputType = (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
272 
273         info.setInitialSurroundingText(testInitialText);
274 
275         assertExpectedTextLength(info,
276                 /* expectBeforeCursorLength= */null,
277                 /* expectSelectionLength= */null,
278                 /* expectAfterCursorLength= */null,
279                 /* expectSurroundingText= */null);
280     }
281 
282     @Test
testInitialSurroundingText_cursorAtHead_emptyBeforeCursorText()283     public void testInitialSurroundingText_cursorAtHead_emptyBeforeCursorText() {
284         final EditorInfo info = new EditorInfo();
285         final CharSequence testText = createTestText(TEST_TEXT_LENGTH);
286         final int selLength = 10;
287         info.initialSelStart = 0;
288         info.initialSelEnd = info.initialSelStart + selLength;
289         final int expectedTextBeforeCursorLength = 0;
290         final int expectedTextAfterCursorLength = testText.length() - selLength;
291         final SurroundingText expectedSurroundingText =
292                 new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
293 
294         info.setInitialSurroundingText(testText);
295 
296         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
297                 expectedTextAfterCursorLength, expectedSurroundingText);
298     }
299 
300     @Test
testInitialSurroundingText_cursorAtTail_emptyAfterCursorText()301     public void testInitialSurroundingText_cursorAtTail_emptyAfterCursorText() {
302         final EditorInfo info = new EditorInfo();
303         final CharSequence testText = createTestText(TEST_TEXT_LENGTH);
304         final int selLength = 10;
305         info.initialSelStart = testText.length() - selLength;
306         info.initialSelEnd = testText.length();
307         final int expectedTextBeforeCursorLength = testText.length() - selLength;
308         final int expectedTextAfterCursorLength = 0;
309         final SurroundingText expectedSurroundingText =
310                 new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
311 
312         info.setInitialSurroundingText(testText);
313 
314         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
315                 expectedTextAfterCursorLength, expectedSurroundingText);
316     }
317 
318     @Test
testInitialSurroundingText_noSelection_emptySelectionText()319     public void testInitialSurroundingText_noSelection_emptySelectionText() {
320         final EditorInfo info = new EditorInfo();
321         final CharSequence testText = createTestText(TEST_TEXT_LENGTH);
322         final int selLength = 0;
323         info.initialSelStart = 0;
324         info.initialSelEnd = info.initialSelStart + selLength;
325         final int expectedTextBeforeCursorLength = 0;
326         final int expectedTextAfterCursorLength = testText.length();
327         final SurroundingText expectedSurroundingText =
328                 new SurroundingText(testText, info.initialSelStart, info.initialSelEnd, 0);
329 
330         info.setInitialSurroundingText(testText);
331 
332         assertExpectedTextLength(info, expectedTextBeforeCursorLength, selLength,
333                 expectedTextAfterCursorLength, expectedSurroundingText);
334     }
335 
336     @Test
testInitialSurroundingText_overSizedSelection_keepsBeforeAfterTextValid()337     public void testInitialSurroundingText_overSizedSelection_keepsBeforeAfterTextValid() {
338         final EditorInfo info = new EditorInfo();
339         final CharSequence testText = createTestText(OVER_SIZED_TEXT_LENGTH);
340         final int selLength = OVER_SIZED_TEXT_LENGTH - 2;
341         info.initialSelStart = 1;
342         info.initialSelEnd = info.initialSelStart + selLength;
343         final int expectedTextBeforeCursorLength = 1;
344         final int expectedTextAfterCursorLength = 1;
345         final int offset = info.initialSelStart - expectedTextBeforeCursorLength;
346         final CharSequence beforeCursor = testText.subSequence(offset,
347                 offset + expectedTextBeforeCursorLength);
348         final CharSequence afterCursor = testText.subSequence(info.initialSelEnd,
349                 testText.length());
350         final CharSequence surroundingText = TextUtils.concat(beforeCursor, afterCursor);
351         final SurroundingText expectedSurroundingText =
352                 new SurroundingText(surroundingText, info.initialSelStart, info.initialSelStart, 0);
353 
354         info.setInitialSurroundingText(testText);
355 
356         assertExpectedTextLength(info, expectedTextBeforeCursorLength,
357                 /* expectSelectionLength= */null, expectedTextAfterCursorLength,
358                 expectedSurroundingText);
359 
360     }
361 
362     @Test
testInitialSurroundingSubText_keepsOriginalCursorPosition()363     public void testInitialSurroundingSubText_keepsOriginalCursorPosition() {
364         final EditorInfo info = new EditorInfo();
365         final String prefixString = "prefix";
366         final CharSequence subText = createTestText(TEST_TEXT_LENGTH);
367         final CharSequence originalText = TextUtils.concat(prefixString, subText);
368         final int selLength = 2;
369         info.initialSelStart = originalText.length() / 2;
370         info.initialSelEnd = info.initialSelStart + selLength;
371         final CharSequence expectedTextBeforeCursor = createExpectedText(/* startNumber= */0,
372                 info.initialSelStart - prefixString.length());
373         final CharSequence expectedSelectedText = createExpectedText(
374                 info.initialSelStart - prefixString.length(), selLength);
375         final CharSequence expectedTextAfterCursor = createExpectedText(
376                 info.initialSelEnd - prefixString.length(),
377                 originalText.length() - info.initialSelEnd);
378         final SurroundingText expectedSurroundingText = new SurroundingText(
379                 TextUtils.concat(expectedTextBeforeCursor, expectedSelectedText,
380                         expectedTextAfterCursor),
381                 info.initialSelStart - prefixString.length(),
382                 info.initialSelStart - prefixString.length() + selLength,
383                 prefixString.length());
384 
385         info.setInitialSurroundingSubText(subText, prefixString.length());
386 
387         assertTrue(TextUtils.equals(expectedTextBeforeCursor,
388                 info.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
389                         InputConnection.GET_TEXT_WITH_STYLES)));
390         assertTrue(TextUtils.equals(expectedSelectedText,
391                 info.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES)));
392         assertTrue(TextUtils.equals(expectedTextAfterCursor,
393                 info.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
394                         InputConnection.GET_TEXT_WITH_STYLES)));
395         SurroundingText surroundingText = info.getInitialSurroundingText(
396                 REQUEST_LONGEST_AVAILABLE_TEXT,
397                 REQUEST_LONGEST_AVAILABLE_TEXT,
398                 InputConnection.GET_TEXT_WITH_STYLES);
399         assertNotNull(surroundingText);
400         assertTrue(TextUtils.equals(expectedSurroundingText.getText(), surroundingText.getText()));
401         assertEquals(expectedSurroundingText.getSelectionStart(),
402                 surroundingText.getSelectionStart());
403         assertEquals(expectedSurroundingText.getSelectionEnd(), surroundingText.getSelectionEnd());
404     }
405 
assertExpectedTextLength(EditorInfo editorInfo, Integer expectBeforeCursorLength, Integer expectSelectionLength, Integer expectAfterCursorLength, SurroundingText expectSurroundingText)406     private static void assertExpectedTextLength(EditorInfo editorInfo,
407             Integer expectBeforeCursorLength, Integer expectSelectionLength,
408             Integer expectAfterCursorLength,
409             SurroundingText expectSurroundingText) {
410         final CharSequence textBeforeCursor =
411                 editorInfo.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
412                         InputConnection.GET_TEXT_WITH_STYLES);
413         final CharSequence selectedText =
414                 editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
415         final CharSequence textAfterCursor =
416                 editorInfo.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
417                         InputConnection.GET_TEXT_WITH_STYLES);
418         final SurroundingText surroundingText = editorInfo.getInitialSurroundingText(
419                 REQUEST_LONGEST_AVAILABLE_TEXT,
420                 REQUEST_LONGEST_AVAILABLE_TEXT,
421                 InputConnection.GET_TEXT_WITH_STYLES);
422 
423         if (expectBeforeCursorLength == null) {
424             assertNull(textBeforeCursor);
425         } else {
426             assertEquals(expectBeforeCursorLength.intValue(), textBeforeCursor.length());
427         }
428 
429         if (expectSelectionLength == null) {
430             assertNull(selectedText);
431         } else {
432             assertEquals(expectSelectionLength.intValue(), selectedText.length());
433         }
434 
435         if (expectAfterCursorLength == null) {
436             assertNull(textAfterCursor);
437         } else {
438             assertEquals(expectAfterCursorLength.intValue(), textAfterCursor.length());
439         }
440 
441         if (expectSurroundingText == null) {
442             assertNull(surroundingText);
443         } else {
444             assertTrue(TextUtils.equals(
445                     expectSurroundingText.getText(), surroundingText.getText()));
446             assertEquals(expectSurroundingText.getSelectionStart(),
447                     surroundingText.getSelectionStart());
448             assertEquals(expectSurroundingText.getSelectionEnd(),
449                     surroundingText.getSelectionEnd());
450             assertEquals(expectSurroundingText.getOffset(), surroundingText.getOffset());
451         }
452     }
453 
createTestText(int size)454     private static CharSequence createTestText(int size) {
455         final SpannableStringBuilder builder = new SpannableStringBuilder();
456         for (int i = 0; i < size; i++) {
457             builder.append(Integer.toString(i % 10));
458         }
459         return builder;
460     }
461 
createExpectedText(int startNumber, int length)462     private static CharSequence createExpectedText(int startNumber, int length) {
463         final SpannableStringBuilder builder = new SpannableStringBuilder();
464         for (int i = startNumber; i < startNumber + length; i++) {
465             builder.append(Integer.toString(i % 10));
466         }
467         return builder;
468     }
469 
concatInitialSurroundingText(EditorInfo info)470     private static CharSequence concatInitialSurroundingText(EditorInfo info) {
471         final CharSequence textBeforeCursor =
472                 nullToEmpty(info.getInitialTextBeforeCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
473                         InputConnection.GET_TEXT_WITH_STYLES));
474         final CharSequence selectedText =
475                 nullToEmpty(info.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES));
476         final CharSequence textAfterCursor =
477                 nullToEmpty(info.getInitialTextAfterCursor(REQUEST_LONGEST_AVAILABLE_TEXT,
478                         InputConnection.GET_TEXT_WITH_STYLES));
479 
480         return TextUtils.concat(textBeforeCursor, selectedText, textAfterCursor);
481     }
482 
nullToEmpty(CharSequence source)483     private static CharSequence nullToEmpty(CharSequence source) {
484         return (source == null) ? new SpannableStringBuilder("") : source;
485     }
486 }
487