1 /*
2  * Copyright (C) 2016 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.util;
18 
19 import android.text.Editable;
20 import android.text.Selection;
21 import android.text.SpannableStringBuilder;
22 import android.view.View;
23 import android.view.inputmethod.BaseInputConnection;
24 
25 import androidx.annotation.NonNull;
26 import androidx.test.platform.app.InstrumentationRegistry;
27 
28 public final class InputConnectionTestUtils {
29 
30     private static final String U1F427 = "\uD83D\uDC27";
31 
32     /**
33      * A utility function to generate test string for input method APIs.  There are several
34      * pre-defined meta characters that are useful for unit tests.
35      *
36      * <p>Pre-defined meta characters:</p>
37      * <dl>
38      *     <dl>{@code [}</dl><dd>The text selection starts from here.</dd>
39      *     <dl>{@code ]}</dl><dd>The text selection ends at here.</dd>
40      *     <dl>{@code <}</dl><dd>Represents a high surrogate character.</dd>
41      *     <dl>{@code >}</dl><dd>Represents a low surrogate character.</dd>
42      * </ul>
43      *
44      * <p>Examples: {@code "012[3<>67]89"} will be converted to {@ode "0123HL6789"}, where
45      * {@code "H"} and {@code "L"} indicate certain high and low surrogate characters, respectively,
46      * with selecting {@code "3HL67"}.</p>
47      *
48      * @param formatString
49      * @return A {@link CharSequence} object with text selection specified by the meta characters.
50      */
formatString(final String formatString)51     public static CharSequence formatString(final String formatString) {
52         final SpannableStringBuilder builder = new SpannableStringBuilder();
53         int selectionStart = -1;
54         int selectionEnd = -1;
55         for (int i = 0; i < formatString.length(); ++i) {
56             final Character c = formatString.charAt(i);
57             switch (c) {
58                 case '[':
59                     selectionStart = builder.length();
60                     break;
61                 case ']':
62                     selectionEnd = builder.length();
63                     break;
64                 case '<':
65                     builder.append(U1F427.charAt(0));  // High surrogate
66                     break;
67                 case '>':
68                     builder.append(U1F427.charAt(1));  // Low surrogate
69                     break;
70                 default:
71                     builder.append(c);
72                     break;
73             }
74         }
75         if (selectionStart < 0) {
76             throw new UnsupportedOperationException("Selection marker '[' must be specified.");
77         }
78         if (selectionEnd < 0) {
79             throw new UnsupportedOperationException("Selection marker ']' must be specified.");
80         }
81         Selection.setSelection(builder, selectionStart, selectionEnd);
82         return builder;
83     }
84 
85     /**
86      * A utility method to create an instance of {@link BaseInputConnection}.
87      *
88      * @return {@link BaseInputConnection} instantiated in the full editor mode with {@code
89      *     editable}.
90      */
createBaseInputConnection()91     public static BaseInputConnection createBaseInputConnection() {
92         final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
93         return new BaseInputConnection(view, true);
94     }
95 
96     /**
97      * A utility method to create an instance of {@link BaseInputConnection} from {@link Editable}.
98      *
99      * @param editable the initial text.
100      * @return {@link BaseInputConnection} instantiated in the full editor mode with {@code
101      *     editable}.
102      */
createBaseInputConnection(@onNull Editable editable)103     public static BaseInputConnection createBaseInputConnection(@NonNull Editable editable) {
104         final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
105         return new BaseInputConnection(view, true) {
106             @Override
107             public Editable getEditable() {
108                 return editable;
109             }
110         };
111     }
112 
113     /**
114      * A utility method to create an instance of {@link BaseInputConnection} in the full editor mode
115      * with an initial text and selection range.
116      *
117      * @param source the initial text.
118      * @return {@link BaseInputConnection} instantiated in the full editor mode with {@code source}
119      *     and selection range from {@code selectionStart} to {@code selectionEnd}
120      */
121     public static BaseInputConnection createBaseInputConnectionWithSelection(CharSequence source) {
122         final int selectionStart = Selection.getSelectionStart(source);
123         final int selectionEnd = Selection.getSelectionEnd(source);
124         final Editable editable = Editable.Factory.getInstance().newEditable(source);
125         Selection.setSelection(editable, selectionStart, selectionEnd);
126         return createBaseInputConnection(editable);
127     }
128 }
129