InputMethodTest.java revision b21220efae92a56ff7b4b781fa614a6e3a8a3007
1/*
2 * Copyright (C) 2013 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.content.Context;
20import android.content.pm.ApplicationInfo;
21import android.content.pm.ResolveInfo;
22import android.content.pm.ServiceInfo;
23import android.test.InstrumentationTestCase;
24import android.test.suitebuilder.annotation.SmallTest;
25import android.view.inputmethod.InputMethodInfo;
26import android.view.inputmethod.InputMethodSubtype;
27import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
28
29import com.android.internal.inputmethod.InputMethodUtils;
30
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.HashSet;
34import java.util.List;
35import java.util.Locale;
36import java.util.Objects;
37
38public class InputMethodTest extends InstrumentationTestCase {
39    private static final boolean IS_AUX = true;
40    private static final boolean IS_DEFAULT = true;
41    private static final boolean IS_AUTO = true;
42    private static final boolean IS_ASCII_CAPABLE = true;
43    private static final boolean IS_SYSTEM_READY = true;
44    private static final ArrayList<InputMethodSubtype> NO_SUBTYPE = null;
45    private static final Locale LOCALE_EN_US = new Locale("en", "US");
46    private static final Locale LOCALE_EN_GB = new Locale("en", "GB");
47    private static final Locale LOCALE_EN_IN = new Locale("en", "IN");
48    private static final Locale LOCALE_HI = new Locale("hi");
49    private static final Locale LOCALE_JA_JP = new Locale("ja", "JP");
50    private static final Locale LOCALE_ZH_CN = new Locale("zh", "CN");
51    private static final Locale LOCALE_ZH_TW = new Locale("zh", "TW");
52    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
53    private static final String SUBTYPE_MODE_VOICE = "voice";
54
55    @SmallTest
56    public void testVoiceImes() throws Exception {
57        // locale: en_US
58        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
59                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
60        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
61                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme");
62        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_US,
63                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
64        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_US,
65                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
66                "DummyNonDefaultAutoVoiceIme1");
67
68        // locale: en_GB
69        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
70                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
71        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
72                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme");
73        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_EN_GB,
74                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
75        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_EN_GB,
76                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
77                "DummyNonDefaultAutoVoiceIme1");
78
79        // locale: ja_JP
80        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
81                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
82        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
83                !IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme");
84        assertDefaultEnabledImes(getImesWithDefaultVoiceIme(), LOCALE_JA_JP,
85                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyDefaultAutoVoiceIme");
86        assertDefaultEnabledImes(getImesWithoutDefaultVoiceIme(), LOCALE_JA_JP,
87                IS_SYSTEM_READY, "DummyDefaultEnKeyboardIme", "DummyNonDefaultAutoVoiceIme0",
88                "DummyNonDefaultAutoVoiceIme1");
89    }
90
91    @SmallTest
92    public void testKeyboardImes() throws Exception {
93        // locale: en_US
94        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
95                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
96        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rUS"), LOCALE_EN_US,
97                IS_SYSTEM_READY, "com.android.apps.inputmethod.latin",
98                "com.android.apps.inputmethod.voice");
99
100        // locale: en_GB
101        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
102                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
103        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rGB"), LOCALE_EN_GB,
104                IS_SYSTEM_READY, "com.android.apps.inputmethod.latin",
105                "com.android.apps.inputmethod.voice");
106
107        // locale: en_IN
108        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
109                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
110        assertDefaultEnabledImes(getSamplePreinstalledImes("en-rIN"), LOCALE_EN_IN,
111                IS_SYSTEM_READY, "com.android.apps.inputmethod.hindi",
112                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
113
114        // locale: hi
115        assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
116                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
117        assertDefaultEnabledImes(getSamplePreinstalledImes("hi"), LOCALE_HI,
118                IS_SYSTEM_READY, "com.android.apps.inputmethod.hindi",
119                "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.voice");
120
121        // locale: ja_JP
122        assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
123                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
124        assertDefaultEnabledImes(getSamplePreinstalledImes("ja-rJP"), LOCALE_JA_JP,
125                IS_SYSTEM_READY, "com.android.apps.inputmethod.japanese",
126                "com.android.apps.inputmethod.voice");
127
128        // locale: zh_CN
129        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
130                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
131        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rCN"), LOCALE_ZH_CN,
132                IS_SYSTEM_READY, "com.android.apps.inputmethod.pinyin",
133                "com.android.apps.inputmethod.voice");
134
135        // locale: zh_TW
136        // Note: In this case, no IME is suitable for the system locale. Hence we will pick up a
137        // fallback IME regardless of the "default" attribute.
138        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
139                !IS_SYSTEM_READY, "com.android.apps.inputmethod.latin");
140        assertDefaultEnabledImes(getSamplePreinstalledImes("zh-rTW"), LOCALE_ZH_TW,
141                IS_SYSTEM_READY, "com.android.apps.inputmethod.latin",
142                "com.android.apps.inputmethod.voice");
143    }
144
145    @SmallTest
146    public void testParcelable() throws Exception {
147        final ArrayList<InputMethodInfo> originalList = getSamplePreinstalledImes("en-rUS");
148        final List<InputMethodInfo> clonedList = cloneViaParcel(originalList);
149        assertNotNull(clonedList);
150        final List<InputMethodInfo> clonedClonedList = cloneViaParcel(clonedList);
151        assertNotNull(clonedClonedList);
152        assertEquals(originalList, clonedList);
153        assertEquals(clonedList, clonedClonedList);
154        assertEquals(originalList.size(), clonedList.size());
155        assertEquals(clonedList.size(), clonedClonedList.size());
156        for (int imeIndex = 0; imeIndex < originalList.size(); ++imeIndex) {
157            verifyEquality(originalList.get(imeIndex), clonedList.get(imeIndex));
158            verifyEquality(clonedList.get(imeIndex), clonedClonedList.get(imeIndex));
159        }
160    }
161
162    private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
163            final Locale systemLocale, final boolean isSystemReady, String... expectedImeNames) {
164        final Context context = getInstrumentation().getTargetContext();
165        final String[] actualImeNames = getPackageNames(callGetDefaultEnabledImesUnderWithLocale(
166                context, isSystemReady, preinstalledImes, systemLocale));
167        assertEquals(expectedImeNames.length, actualImeNames.length);
168        for (int i = 0; i < expectedImeNames.length; ++i) {
169            assertEquals(expectedImeNames[i], actualImeNames[i]);
170        }
171    }
172
173    private static List<InputMethodInfo> cloneViaParcel(final List<InputMethodInfo> list) {
174        Parcel p = null;
175        try {
176            p = Parcel.obtain();
177            p.writeTypedList(list);
178            p.setDataPosition(0);
179            return p.createTypedArrayList(InputMethodInfo.CREATOR);
180        } finally {
181            if (p != null) {
182                p.recycle();
183            }
184        }
185    }
186
187    private static ArrayList<InputMethodInfo> callGetDefaultEnabledImesUnderWithLocale(
188            final Context context, final boolean isSystemReady,
189            final ArrayList<InputMethodInfo> imis, final Locale locale) {
190        final Locale initialLocale = context.getResources().getConfiguration().locale;
191        try {
192            context.getResources().getConfiguration().setLocale(locale);
193            return InputMethodUtils.getDefaultEnabledImes(context, isSystemReady, imis);
194        } finally {
195            context.getResources().getConfiguration().setLocale(initialLocale);
196        }
197    }
198
199    private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
200        final String[] packageNames = new String[imis.size()];
201        for (int i = 0; i < imis.size(); ++i) {
202            packageNames[i] = imis.get(i).getPackageName();
203        }
204        return packageNames;
205    }
206
207    private static void verifyEquality(InputMethodInfo expected, InputMethodInfo actual) {
208        assertEquals(expected, actual);
209        assertEquals(expected.getSubtypeCount(), actual.getSubtypeCount());
210        for (int subtypeIndex = 0; subtypeIndex < expected.getSubtypeCount(); ++subtypeIndex) {
211            final InputMethodSubtype expectedSubtype = expected.getSubtypeAt(subtypeIndex);
212            final InputMethodSubtype actualSubtype = actual.getSubtypeAt(subtypeIndex);
213            assertEquals(expectedSubtype, actualSubtype);
214            assertEquals(expectedSubtype.hashCode(), actualSubtype.hashCode());
215        }
216    }
217
218    private static InputMethodInfo createDummyInputMethodInfo(String packageName, String name,
219            CharSequence label, boolean isAuxIme, boolean isDefault,
220            List<InputMethodSubtype> subtypes) {
221        final ResolveInfo ri = new ResolveInfo();
222        final ServiceInfo si = new ServiceInfo();
223        final ApplicationInfo ai = new ApplicationInfo();
224        ai.packageName = packageName;
225        ai.enabled = true;
226        ai.flags |= ApplicationInfo.FLAG_SYSTEM;
227        si.applicationInfo = ai;
228        si.enabled = true;
229        si.packageName = packageName;
230        si.name = name;
231        si.exported = true;
232        si.nonLocalizedLabel = label;
233        ri.serviceInfo = si;
234        return new InputMethodInfo(ri, isAuxIme, "", subtypes, 1, isDefault);
235    }
236
237    private static InputMethodSubtype createDummyInputMethodSubtype(String locale, String mode,
238            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
239            boolean isAsciiCapable) {
240        return new InputMethodSubtypeBuilder()
241                .setSubtypeNameResId(0)
242                .setSubtypeIconResId(0)
243                .setSubtypeLocale(locale)
244                .setSubtypeMode(mode)
245                .setSubtypeExtraValue("")
246                .setIsAuxiliary(isAuxiliary)
247                .setOverridesImplicitlyEnabledSubtype(overridesImplicitlyEnabledSubtype)
248                .setIsAsciiCapable(isAsciiCapable)
249                .build();
250    }
251
252    private static ArrayList<InputMethodInfo> getImesWithDefaultVoiceIme() {
253        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
254        {
255            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
256            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX, IS_AUTO,
257                    !IS_ASCII_CAPABLE));
258            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
259                    !IS_AUTO, !IS_ASCII_CAPABLE));
260            preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultAutoVoiceIme",
261                    "dummy.voice0", "DummyVoice0", IS_AUX, IS_DEFAULT, subtypes));
262        }
263        preinstalledImes.addAll(getImesWithoutDefaultVoiceIme());
264        return preinstalledImes;
265    }
266
267    private static ArrayList<InputMethodInfo> getImesWithoutDefaultVoiceIme() {
268        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
269        {
270            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
271            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX, IS_AUTO,
272                    !IS_ASCII_CAPABLE));
273            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
274                    !IS_AUTO, !IS_ASCII_CAPABLE));
275            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme0",
276                    "dummy.voice1", "DummyVoice1", IS_AUX, !IS_DEFAULT, subtypes));
277        }
278        {
279            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
280            subtypes.add(createDummyInputMethodSubtype("auto", SUBTYPE_MODE_VOICE, IS_AUX, IS_AUTO,
281                    !IS_ASCII_CAPABLE));
282            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
283                    !IS_AUTO, !IS_ASCII_CAPABLE));
284            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultAutoVoiceIme1",
285                    "dummy.voice2", "DummyVoice2", IS_AUX, !IS_DEFAULT, subtypes));
286        }
287        {
288            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
289            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_VOICE, IS_AUX,
290                    !IS_AUTO, !IS_ASCII_CAPABLE));
291            preinstalledImes.add(createDummyInputMethodInfo("DummyNonDefaultVoiceIme2",
292                    "dummy.voice3", "DummyVoice3", IS_AUX, !IS_DEFAULT, subtypes));
293        }
294        {
295            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
296            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
297                    !IS_AUTO, IS_ASCII_CAPABLE));
298            preinstalledImes.add(createDummyInputMethodInfo("DummyDefaultEnKeyboardIme",
299                    "dummy.keyboard0", "DummyKeyboard0", !IS_AUX, IS_DEFAULT, subtypes));
300        }
301        return preinstalledImes;
302    }
303
304    private static boolean contains(final String[] textList, final String textToBeChecked) {
305        if (textList == null) {
306            return false;
307        }
308        for (final String text : textList) {
309            if (Objects.equals(textToBeChecked, text)) {
310                return true;
311            }
312        }
313        return false;
314    }
315
316    private static ArrayList<InputMethodInfo> getSamplePreinstalledImes(final String localeString) {
317        ArrayList<InputMethodInfo> preinstalledImes = new ArrayList<>();
318
319        // a dummy Voice IME
320        {
321            final boolean isDefaultIme = false;
322            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
323            subtypes.add(createDummyInputMethodSubtype("", SUBTYPE_MODE_VOICE, IS_AUX,
324                    IS_AUTO, !IS_ASCII_CAPABLE));
325            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.voice",
326                    "com.android.inputmethod.voice", "DummyVoiceIme", IS_AUX, isDefaultIme,
327                    subtypes));
328        }
329        // a dummy Hindi IME
330        {
331            final boolean isDefaultIme = contains(new String[]{ "hi", "en-rIN" }, localeString);
332            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
333            // TODO: This subtype should be marked as IS_ASCII_CAPABLE
334            subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
335                    !IS_AUTO, !IS_ASCII_CAPABLE));
336            subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
337                    !IS_AUTO, !IS_ASCII_CAPABLE));
338            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.hindi",
339                    "com.android.inputmethod.hindi", "DummyHindiIme", !IS_AUX, isDefaultIme,
340                    subtypes));
341        }
342
343        // a dummy Pinyin IME
344        {
345            final boolean isDefaultIme = contains(new String[]{ "zh-rCN" }, localeString);
346            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
347            subtypes.add(createDummyInputMethodSubtype("zh_CN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
348                    !IS_AUTO, !IS_ASCII_CAPABLE));
349            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.pinyin",
350                    "com.android.apps.inputmethod.pinyin", "DummyPinyinIme", !IS_AUX, isDefaultIme,
351                    subtypes));
352        }
353
354        // a dummy Korean IME
355        {
356            final boolean isDefaultIme = contains(new String[]{ "ko" }, localeString);
357            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
358            subtypes.add(createDummyInputMethodSubtype("ko", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
359                    !IS_AUTO, !IS_ASCII_CAPABLE));
360            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.korean",
361                    "com.android.apps.inputmethod.korean", "DummyKoreanIme", !IS_AUX, isDefaultIme,
362                    subtypes));
363        }
364
365        // a dummy Latin IME
366        {
367            final boolean isDefaultIme = contains(
368                    new String[]{ "en-rUS", "en-rGB", "en-rIN", "en", "hi" }, localeString);
369            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
370            subtypes.add(createDummyInputMethodSubtype("en_US", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
371                    !IS_AUTO, IS_ASCII_CAPABLE));
372            subtypes.add(createDummyInputMethodSubtype("en_GB", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
373                    !IS_AUTO, IS_ASCII_CAPABLE));
374            subtypes.add(createDummyInputMethodSubtype("en_IN", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
375                    !IS_AUTO, IS_ASCII_CAPABLE));
376            subtypes.add(createDummyInputMethodSubtype("hi", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
377                    !IS_AUTO, IS_ASCII_CAPABLE));
378            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.latin",
379                    "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, isDefaultIme,
380                    subtypes));
381        }
382
383        // a dummy Japanese IME
384        {
385            final boolean isDefaultIme = contains(new String[]{ "ja", "ja-rJP" }, localeString);
386            final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
387            subtypes.add(createDummyInputMethodSubtype("ja", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
388                    !IS_AUTO, !IS_ASCII_CAPABLE));
389            subtypes.add(createDummyInputMethodSubtype("emoji", SUBTYPE_MODE_KEYBOARD, !IS_AUX,
390                    !IS_AUTO, !IS_ASCII_CAPABLE));
391            preinstalledImes.add(createDummyInputMethodInfo("com.android.apps.inputmethod.japanese",
392                    "com.android.apps.inputmethod.japanese", "DummyJapaneseIme", !IS_AUX,
393                    isDefaultIme, subtypes));
394        }
395
396        return preinstalledImes;
397    }
398}
399