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 com.android.internal.inputmethod;
18
19import android.content.pm.ApplicationInfo;
20import android.content.pm.ResolveInfo;
21import android.content.pm.ServiceInfo;
22import android.test.InstrumentationTestCase;
23import android.test.suitebuilder.annotation.SmallTest;
24import android.view.inputmethod.InputMethodInfo;
25import android.view.inputmethod.InputMethodSubtype;
26import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
27
28import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl;
29import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
30
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.List;
34
35public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTestCase {
36    private static final String DUMMY_PACKAGE_NAME = "dymmy package name";
37    private static final String DUMMY_SETTING_ACTIVITY_NAME = "";
38    private static final boolean DUMMY_IS_AUX_IME = false;
39    private static final boolean DUMMY_FORCE_DEFAULT = false;
40    private static final int DUMMY_IS_DEFAULT_RES_ID = 0;
41    private static final String SYSTEM_LOCALE = "en_US";
42    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
43
44    private static InputMethodSubtype createDummySubtype(final String locale) {
45        final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
46        return builder.setSubtypeNameResId(0)
47                .setSubtypeIconResId(0)
48                .setSubtypeLocale(locale)
49                .setIsAsciiCapable(true)
50                .build();
51    }
52
53    private static void addDummyImeSubtypeListItems(List<ImeSubtypeListItem> items,
54            String imeName, String imeLabel, List<String> subtypeLocales,
55            boolean supportsSwitchingToNextInputMethod) {
56        final ResolveInfo ri = new ResolveInfo();
57        final ServiceInfo si = new ServiceInfo();
58        final ApplicationInfo ai = new ApplicationInfo();
59        ai.packageName = DUMMY_PACKAGE_NAME;
60        ai.enabled = true;
61        si.applicationInfo = ai;
62        si.enabled = true;
63        si.packageName = DUMMY_PACKAGE_NAME;
64        si.name = imeName;
65        si.exported = true;
66        si.nonLocalizedLabel = imeLabel;
67        ri.serviceInfo = si;
68        List<InputMethodSubtype> subtypes = null;
69        if (subtypeLocales != null) {
70            subtypes = new ArrayList<>();
71            for (String subtypeLocale : subtypeLocales) {
72                subtypes.add(createDummySubtype(subtypeLocale));
73            }
74        }
75        final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME,
76                DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID,
77                DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod);
78        if (subtypes == null) {
79            items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
80                    NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE));
81        } else {
82            for (int i = 0; i < subtypes.size(); ++i) {
83                final String subtypeLocale = subtypeLocales.get(i);
84                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
85                        SYSTEM_LOCALE));
86            }
87        }
88    }
89
90    private static List<ImeSubtypeListItem> createEnabledImeSubtypes() {
91        final List<ImeSubtypeListItem> items = new ArrayList<>();
92        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"),
93                true /* supportsSwitchingToNextInputMethod*/);
94        addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme",
95                Arrays.asList("en_UK", "hi"),
96                false /* supportsSwitchingToNextInputMethod*/);
97        addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null,
98                false /* supportsSwitchingToNextInputMethod*/);
99        addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"),
100                true /* supportsSwitchingToNextInputMethod*/);
101        addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme",
102                Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/);
103        return items;
104    }
105
106    private static List<ImeSubtypeListItem> createDisabledImeSubtypes() {
107        final List<ImeSubtypeListItem> items = new ArrayList<>();
108        addDummyImeSubtypeListItems(items,
109                "UnknownIme", "UnknownIme",
110                Arrays.asList("en_US", "hi"),
111                true /* supportsSwitchingToNextInputMethod*/);
112        addDummyImeSubtypeListItems(items,
113                "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme",
114                Arrays.asList("en_US"),
115                false /* supportsSwitchingToNextInputMethod*/);
116        addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme",
117                "UnknownSubtypeUnawareIme", null,
118                false /* supportsSwitchingToNextInputMethod*/);
119        return items;
120    }
121
122    private void assertNextInputMethod(final ControllerImpl controller,
123            final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem,
124            final ImeSubtypeListItem nextItem, final ImeSubtypeListItem prevItem) {
125        InputMethodSubtype subtype = null;
126        if (currentItem.mSubtypeName != null) {
127            subtype = createDummySubtype(currentItem.mSubtypeName.toString());
128        }
129        final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme,
130                currentItem.mImi, subtype, true /* forward */);
131        assertEquals(nextItem, nextIme);
132        final ImeSubtypeListItem prevIme = controller.getNextInputMethod(onlyCurrentIme,
133                currentItem.mImi, subtype, false /* forward */);
134        assertEquals(prevItem, prevIme);
135    }
136
137    private void assertRotationOrder(final ControllerImpl controller,
138            final boolean onlyCurrentIme,
139            final ImeSubtypeListItem... expectedRotationOrderOfImeSubtypeList) {
140        final int N = expectedRotationOrderOfImeSubtypeList.length;
141        for (int i = 0; i < N; i++) {
142            final int currentIndex = i;
143            final int prevIndex = (currentIndex + N - 1) % N;
144            final int nextIndex = (currentIndex + 1) % N;
145            final ImeSubtypeListItem currentItem =
146                    expectedRotationOrderOfImeSubtypeList[currentIndex];
147            final ImeSubtypeListItem nextItem = expectedRotationOrderOfImeSubtypeList[nextIndex];
148            final ImeSubtypeListItem prevItem = expectedRotationOrderOfImeSubtypeList[prevIndex];
149            assertNextInputMethod(controller, onlyCurrentIme, currentItem, nextItem, prevItem);
150        }
151    }
152
153    private void onUserAction(final ControllerImpl controller,
154            final ImeSubtypeListItem subtypeListItem) {
155        InputMethodSubtype subtype = null;
156        if (subtypeListItem.mSubtypeName != null) {
157            subtype = createDummySubtype(subtypeListItem.mSubtypeName.toString());
158        }
159        controller.onUserActionLocked(subtypeListItem.mImi, subtype);
160    }
161
162    @SmallTest
163    public void testControllerImpl() throws Exception {
164        final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes();
165        final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0);
166        final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1);
167        final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2);
168        final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3);
169
170        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
171        final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
172        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
173        final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
174        final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
175        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
176        final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
177        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
178
179        final ControllerImpl controller = ControllerImpl.createFrom(
180                null /* currentInstance */, enabledItems);
181
182        // switching-aware loop
183        assertRotationOrder(controller, false /* onlyCurrentIme */,
184                latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
185
186        // switching-unaware loop
187        assertRotationOrder(controller, false /* onlyCurrentIme */,
188                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
189                switchUnawareJapaneseIme_ja_JP);
190
191        // test onlyCurrentIme == true
192        assertRotationOrder(controller, true /* onlyCurrentIme */,
193                latinIme_en_US, latinIme_fr);
194        assertRotationOrder(controller, true /* onlyCurrentIme */,
195                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
196        assertNextInputMethod(controller, true /* onlyCurrentIme */,
197                subtypeUnawareIme, null, null);
198        assertNextInputMethod(controller, true /* onlyCurrentIme */,
199                japaneseIme_ja_JP, null, null);
200        assertNextInputMethod(controller, true /* onlyCurrentIme */,
201                switchUnawareJapaneseIme_ja_JP, null, null);
202
203        // Make sure that disabled IMEs are not accepted.
204        assertNextInputMethod(controller, false /* onlyCurrentIme */,
205                disabledIme_en_US, null, null);
206        assertNextInputMethod(controller, false /* onlyCurrentIme */,
207                disabledIme_hi, null, null);
208        assertNextInputMethod(controller, false /* onlyCurrentIme */,
209                disabledSwitchingUnawareIme, null, null);
210        assertNextInputMethod(controller, false /* onlyCurrentIme */,
211                disabledSubtypeUnawareIme, null, null);
212        assertNextInputMethod(controller, true /* onlyCurrentIme */,
213                disabledIme_en_US, null, null);
214        assertNextInputMethod(controller, true /* onlyCurrentIme */,
215                disabledIme_hi, null, null);
216        assertNextInputMethod(controller, true /* onlyCurrentIme */,
217                disabledSwitchingUnawareIme, null, null);
218        assertNextInputMethod(controller, true /* onlyCurrentIme */,
219                disabledSubtypeUnawareIme, null, null);
220    }
221
222    @SmallTest
223    public void testControllerImplWithUserAction() throws Exception {
224        final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes();
225        final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0);
226        final ImeSubtypeListItem latinIme_fr = enabledItems.get(1);
227        final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2);
228        final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3);
229        final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4);
230        final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5);
231        final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6);
232
233        final ControllerImpl controller = ControllerImpl.createFrom(
234                null /* currentInstance */, enabledItems);
235
236        // === switching-aware loop ===
237        assertRotationOrder(controller, false /* onlyCurrentIme */,
238                latinIme_en_US, latinIme_fr, japaneseIme_ja_JP);
239        // Then notify that a user did something for latinIme_fr.
240        onUserAction(controller, latinIme_fr);
241        assertRotationOrder(controller, false /* onlyCurrentIme */,
242                latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
243        // Then notify that a user did something for latinIme_fr again.
244        onUserAction(controller, latinIme_fr);
245        assertRotationOrder(controller, false /* onlyCurrentIme */,
246                latinIme_fr, latinIme_en_US, japaneseIme_ja_JP);
247        // Then notify that a user did something for japaneseIme_ja_JP.
248        onUserAction(controller, latinIme_fr);
249        assertRotationOrder(controller, false /* onlyCurrentIme */,
250                japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
251        // Check onlyCurrentIme == true.
252        assertNextInputMethod(controller, true /* onlyCurrentIme */,
253                japaneseIme_ja_JP, null, null);
254        assertRotationOrder(controller, true /* onlyCurrentIme */,
255                latinIme_fr, latinIme_en_US);
256        assertRotationOrder(controller, true /* onlyCurrentIme */,
257                latinIme_en_US, latinIme_fr);
258
259        // === switching-unaware loop ===
260        assertRotationOrder(controller, false /* onlyCurrentIme */,
261                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
262                switchUnawareJapaneseIme_ja_JP);
263        // User action should be ignored for switching unaware IMEs.
264        onUserAction(controller, switchingUnawarelatinIme_hi);
265        assertRotationOrder(controller, false /* onlyCurrentIme */,
266                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
267                switchUnawareJapaneseIme_ja_JP);
268        // User action should be ignored for switching unaware IMEs.
269        onUserAction(controller, switchUnawareJapaneseIme_ja_JP);
270        assertRotationOrder(controller, false /* onlyCurrentIme */,
271                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
272                switchUnawareJapaneseIme_ja_JP);
273        // Check onlyCurrentIme == true.
274        assertRotationOrder(controller, true /* onlyCurrentIme */,
275                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi);
276        assertNextInputMethod(controller, true /* onlyCurrentIme */,
277                subtypeUnawareIme, null, null);
278        assertNextInputMethod(controller, true /* onlyCurrentIme */,
279                switchUnawareJapaneseIme_ja_JP, null, null);
280
281        // Rotation order should be preserved when created with the same subtype list.
282        final List<ImeSubtypeListItem> sameEnabledItems = createEnabledImeSubtypes();
283        final ControllerImpl newController = ControllerImpl.createFrom(controller,
284                sameEnabledItems);
285        assertRotationOrder(newController, false /* onlyCurrentIme */,
286                japaneseIme_ja_JP, latinIme_fr, latinIme_en_US);
287        assertRotationOrder(newController, false /* onlyCurrentIme */,
288                switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi, subtypeUnawareIme,
289                switchUnawareJapaneseIme_ja_JP);
290
291        // Rotation order should be initialized when created with a different subtype list.
292        final List<ImeSubtypeListItem> differentEnabledItems = Arrays.asList(
293                latinIme_en_US, latinIme_fr, switchingUnawarelatinIme_en_UK,
294                switchUnawareJapaneseIme_ja_JP);
295        final ControllerImpl anotherController = ControllerImpl.createFrom(controller,
296                differentEnabledItems);
297        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
298                latinIme_en_US, latinIme_fr);
299        assertRotationOrder(anotherController, false /* onlyCurrentIme */,
300                switchingUnawarelatinIme_en_UK, switchUnawareJapaneseIme_ja_JP);
301    }
302
303    @SmallTest
304    public void testImeSubtypeListItem() throws Exception {
305        final List<ImeSubtypeListItem> items = new ArrayList<>();
306        addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme",
307                Arrays.asList("en_US", "fr", "en", "en_uk", "enn", "e", "EN_US"),
308                true /* supportsSwitchingToNextInputMethod*/);
309        final ImeSubtypeListItem item_en_US = items.get(0);
310        final ImeSubtypeListItem item_fr = items.get(1);
311        final ImeSubtypeListItem item_en = items.get(2);
312        final ImeSubtypeListItem item_enn = items.get(3);
313        final ImeSubtypeListItem item_e = items.get(4);
314        final ImeSubtypeListItem item_EN_US = items.get(5);
315
316        assertTrue(item_en_US.mIsSystemLocale);
317        assertFalse(item_fr.mIsSystemLocale);
318        assertFalse(item_en.mIsSystemLocale);
319        assertFalse(item_en.mIsSystemLocale);
320        assertFalse(item_enn.mIsSystemLocale);
321        assertFalse(item_e.mIsSystemLocale);
322        assertFalse(item_EN_US.mIsSystemLocale);
323
324        assertTrue(item_en_US.mIsSystemLanguage);
325        assertFalse(item_fr.mIsSystemLanguage);
326        assertTrue(item_en.mIsSystemLanguage);
327        assertFalse(item_enn.mIsSystemLocale);
328        assertFalse(item_e.mIsSystemLocale);
329        assertFalse(item_EN_US.mIsSystemLocale);
330    }
331}
332