Loading core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +102 −38 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.internal.inputmethod; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; Loading Loading @@ -211,53 +212,110 @@ public class InputMethodSubtypeSwitchingController { } } private final InputMethodSettings mSettings; private InputMethodAndSubtypeList mSubtypeList; private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) : NOT_A_SUBTYPE_ID; } @VisibleForTesting public static ImeSubtypeListItem getNextInputMethodLockedImpl(List<ImeSubtypeListItem> imList, boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { private static class StaticRotationList { private final List<ImeSubtypeListItem> mImeSubtypeList; public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { mImeSubtypeList = imeSubtypeList; } /** * Returns the index of the specified input method and subtype in the given list. * @param imi The {@link InputMethodInfo} to be searched. * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method * does not have a subtype. * @return The index in the given list. -1 if not found. */ private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { final int currentSubtypeId = calculateSubtypeId(imi, subtype); final int N = mImeSubtypeList.size(); for (int i = 0; i < N; ++i) { final ImeSubtypeListItem isli = mImeSubtypeList.get(i); // Skip until the current IME/subtype is found. if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { return i; } } return -1; } public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { if (imi == null) { return null; } if (imList.size() <= 1) { if (mImeSubtypeList.size() <= 1) { return null; } // Here we have two rotation groups, depending on the returned boolean value of // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}. final boolean expectedValueOfSupportsSwitchingToNextInputMethod = imi.supportsSwitchingToNextInputMethod(); final int N = imList.size(); final int currentSubtypeId = subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) : NOT_A_SUBTYPE_ID; for (int i = 0; i < N; ++i) { final ImeSubtypeListItem isli = imList.get(i); // Skip until the current IME/subtype is found. if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) { continue; } // Found the current IME/subtype. Start searching the next IME/subtype from here. for (int j = 0; j < N - 1; ++j) { final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); // Skip if the candidate doesn't belong to the expected rotation group. if (expectedValueOfSupportsSwitchingToNextInputMethod != candidate.mImi.supportsSwitchingToNextInputMethod()) { continue; final int currentIndex = getIndex(imi, subtype); if (currentIndex < 0) { return null; } final int N = mImeSubtypeList.size(); for (int offset = 1; offset < N; ++offset) { // Start searching the next IME/subtype from the next of the current index. final int candidateIndex = (currentIndex + offset) % N; final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); // Skip if searching inside the current IME only, but the candidate is not // the current IME. if (onlyCurrentIme && !candidate.mImi.equals(imi)) { if (onlyCurrentIme && !imi.equals(candidate.mImi)) { continue; } return candidate; } // No appropriate IME/subtype is found in the list. Give up. return null; } // The current IME/subtype is not found in the list. Give up. } @VisibleForTesting public static class ControllerImpl { private final StaticRotationList mSwitchingAwareSubtypeList; private final StaticRotationList mSwitchingUnawareSubtypeList; public ControllerImpl(final List<ImeSubtypeListItem> sortedItems) { mSwitchingAwareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, true /* supportsSwitchingToNextInputMethod */)); mSwitchingUnawareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, false /* supportsSwitchingToNextInputMethod */)); } public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { if (imi == null) { return null; } if (imi.supportsSwitchingToNextInputMethod()) { return mSwitchingAwareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, subtype); } else { return mSwitchingUnawareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, subtype); } } private static List<ImeSubtypeListItem> filterImeSubtypeList( final List<ImeSubtypeListItem> items, final boolean supportsSwitchingToNextInputMethod) { final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); final int ALL_ITEMS_COUNT = items.size(); for (int i = 0; i < ALL_ITEMS_COUNT; i++) { final ImeSubtypeListItem item = items.get(i); if (item.mImi.supportsSwitchingToNextInputMethod() == supportsSwitchingToNextInputMethod) { result.add(item); } } return result; } } private final InputMethodSettings mSettings; private InputMethodAndSubtypeList mSubtypeList; private ControllerImpl mController; private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) { mSettings = settings; Loading @@ -276,12 +334,18 @@ public class InputMethodSubtypeSwitchingController { public void resetCircularListLocked(Context context) { mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); mController = new ControllerImpl(mSubtypeList.getSortedInputMethodAndSubtypeList()); } public ImeSubtypeListItem getNextInputMethodLocked( boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { return getNextInputMethodLockedImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(), onlyCurrentIme, imi, subtype); public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { if (mController == null) { if (DEBUG) { Log.e(TAG, "mController shouldn't be null."); } return null; } return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, Loading core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +120 −119 Original line number Diff line number Diff line Loading @@ -25,8 +25,9 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; import com.android.internal.inputmethod.InputMethodUtils; import java.util.ArrayList; import java.util.Arrays; Loading @@ -39,6 +40,7 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe private static final boolean DUMMY_FORCE_DEFAULT = false; private static final int DUMMY_IS_DEFAULT_RES_ID = 0; private static final String SYSTEM_LOCALE = "en_US"; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static InputMethodSubtype createDummySubtype(final String locale) { final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); Loading @@ -64,142 +66,141 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe si.exported = true; si.nonLocalizedLabel = imeLabel; ri.serviceInfo = si; final List<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); List<InputMethodSubtype> subtypes = null; if (subtypeLocales != null) { subtypes = new ArrayList<InputMethodSubtype>(); for (String subtypeLocale : subtypeLocales) { subtypes.add(createDummySubtype(subtypeLocale)); } } final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod); if (subtypes == null) { items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); } else { for (int i = 0; i < subtypes.size(); ++i) { final String subtypeLocale = subtypeLocales.get(i); items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, SYSTEM_LOCALE)); } } } private static List<ImeSubtypeListItem> createTestData() { private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); addDummyImeSubtypeListItems(items, "switchAwareLatinIme", "switchAwareLatinIme", Arrays.asList("en_US", "es_US", "fr"), addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), true /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "nonSwitchAwareLatinIme", "nonSwitchAwareLatinIme", addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", Arrays.asList("en_UK", "hi"), false /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "switchAwareJapaneseIme", "switchAwareJapaneseIme", Arrays.asList("ja_JP"), addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"), true /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "nonSwitchAwareJapaneseIme", "nonSwitchAwareJapaneseIme", Arrays.asList("ja_JP"), addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); return items; } private static List<ImeSubtypeListItem> createDisabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); addDummyImeSubtypeListItems(items, "UnknownIme", "UnknownIme", Arrays.asList("en_US", "hi"), true /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme", Arrays.asList("en_US"), false /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", "UnknownSubtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); return items; } @SmallTest public void testGetNextInputMethodImplWithNotOnlyCurrentIme() throws Exception { final List<ImeSubtypeListItem> imList = createTestData(); final boolean ONLY_CURRENT_IME = false; ImeSubtypeListItem currentIme; ImeSubtypeListItem nextIme; // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" currentIme = imList.get(0); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(1), nextIme); // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" currentIme = imList.get(1); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(2), nextIme); // "switchAwareLatinIme/fr" -> "switchAwareJapaneseIme/ja_JP" currentIme = imList.get(2); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(5), nextIme); // "switchAwareJapaneseIme/ja_JP" -> "switchAwareLatinIme/en_US" currentIme = imList.get(5); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(0), nextIme); // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" currentIme = imList.get(3); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(4), nextIme); // "nonSwitchAwareLatinIme/hi" -> "nonSwitchAwareJapaneseIme/ja_JP" currentIme = imList.get(4); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(6), nextIme); // "nonSwitchAwareJapaneseIme/ja_JP" -> "nonSwitchAwareLatinIme/en_UK" currentIme = imList.get(6); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(3), nextIme); private void assertNextInputMethod(final ControllerImpl controller, final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) { InputMethodSubtype subtype = null; if (currentItem.mSubtypeName != null) { subtype = createDummySubtype(currentItem.mSubtypeName.toString()); } final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, currentItem.mImi, subtype); assertEquals(nextItem, nextIme); } @SmallTest public void testGetNextInputMethodImplWithOnlyCurrentIme() throws Exception { final List<ImeSubtypeListItem> imList = createTestData(); final boolean ONLY_CURRENT_IME = true; ImeSubtypeListItem currentIme; ImeSubtypeListItem nextIme; // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" currentIme = imList.get(0); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(1), nextIme); // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" currentIme = imList.get(1); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(2), nextIme); // "switchAwareLatinIme/fr" -> "switchAwareLatinIme/en_US" currentIme = imList.get(2); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(0), nextIme); // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" currentIme = imList.get(3); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(4), nextIme); // "nonSwitchAwareLatinIme/hi" -> "switchAwareLatinIme/en_UK" currentIme = imList.get(4); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(3), nextIme); // "switchAwareJapaneseIme/ja_JP" -> null currentIme = imList.get(5); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertNull(nextIme); // "nonSwitchAwareJapaneseIme/ja_JP" -> null currentIme = imList.get(6); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertNull(nextIme); public void testControllerImpl() throws Exception { final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0); final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2); final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3); final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); final ControllerImpl controller = new ControllerImpl(enabledItems); // switching-aware loop assertNextInputMethod(controller, false /* onlyCurrentIme */, latinIme_en_US, latinIme_fr); assertNextInputMethod(controller, false /* onlyCurrentIme */, latinIme_fr, japaneseIme_ja_JP); assertNextInputMethod(controller, false /* onlyCurrentIme */, japaneseIme_ja_JP, latinIme_en_US); // switching-unaware loop assertNextInputMethod(controller, false /* onlyCurrentIme */, switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); assertNextInputMethod(controller, false /* onlyCurrentIme */, switchingUnawarelatinIme_hi, subtypeUnawareIme); assertNextInputMethod(controller, false /* onlyCurrentIme */, subtypeUnawareIme, switchUnawareJapaneseIme_ja_JP); assertNextInputMethod(controller, false /* onlyCurrentIme */, switchUnawareJapaneseIme_ja_JP, switchingUnawarelatinIme_en_UK); // test onlyCurrentIme == true assertNextInputMethod(controller, true /* onlyCurrentIme */, latinIme_en_US, latinIme_fr); assertNextInputMethod(controller, true /* onlyCurrentIme */, latinIme_fr, latinIme_en_US); assertNextInputMethod(controller, true /* onlyCurrentIme */, switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); assertNextInputMethod(controller, true /* onlyCurrentIme */, switchingUnawarelatinIme_hi, switchingUnawarelatinIme_en_UK); assertNextInputMethod(controller, true /* onlyCurrentIme */, subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, japaneseIme_ja_JP, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, switchUnawareJapaneseIme_ja_JP, null); // Make sure that disabled IMEs are not accepted. assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledIme_en_US, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledIme_hi, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledSwitchingUnawareIme, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledSubtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledIme_en_US, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledIme_hi, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledSwitchingUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledSubtypeUnawareIme, null); } } Loading
core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +102 −38 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.internal.inputmethod; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; import android.util.Log; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; Loading Loading @@ -211,53 +212,110 @@ public class InputMethodSubtypeSwitchingController { } } private final InputMethodSettings mSettings; private InputMethodAndSubtypeList mSubtypeList; private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) : NOT_A_SUBTYPE_ID; } @VisibleForTesting public static ImeSubtypeListItem getNextInputMethodLockedImpl(List<ImeSubtypeListItem> imList, boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { private static class StaticRotationList { private final List<ImeSubtypeListItem> mImeSubtypeList; public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { mImeSubtypeList = imeSubtypeList; } /** * Returns the index of the specified input method and subtype in the given list. * @param imi The {@link InputMethodInfo} to be searched. * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method * does not have a subtype. * @return The index in the given list. -1 if not found. */ private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { final int currentSubtypeId = calculateSubtypeId(imi, subtype); final int N = mImeSubtypeList.size(); for (int i = 0; i < N; ++i) { final ImeSubtypeListItem isli = mImeSubtypeList.get(i); // Skip until the current IME/subtype is found. if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { return i; } } return -1; } public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { if (imi == null) { return null; } if (imList.size() <= 1) { if (mImeSubtypeList.size() <= 1) { return null; } // Here we have two rotation groups, depending on the returned boolean value of // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}. final boolean expectedValueOfSupportsSwitchingToNextInputMethod = imi.supportsSwitchingToNextInputMethod(); final int N = imList.size(); final int currentSubtypeId = subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) : NOT_A_SUBTYPE_ID; for (int i = 0; i < N; ++i) { final ImeSubtypeListItem isli = imList.get(i); // Skip until the current IME/subtype is found. if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) { continue; } // Found the current IME/subtype. Start searching the next IME/subtype from here. for (int j = 0; j < N - 1; ++j) { final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); // Skip if the candidate doesn't belong to the expected rotation group. if (expectedValueOfSupportsSwitchingToNextInputMethod != candidate.mImi.supportsSwitchingToNextInputMethod()) { continue; final int currentIndex = getIndex(imi, subtype); if (currentIndex < 0) { return null; } final int N = mImeSubtypeList.size(); for (int offset = 1; offset < N; ++offset) { // Start searching the next IME/subtype from the next of the current index. final int candidateIndex = (currentIndex + offset) % N; final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); // Skip if searching inside the current IME only, but the candidate is not // the current IME. if (onlyCurrentIme && !candidate.mImi.equals(imi)) { if (onlyCurrentIme && !imi.equals(candidate.mImi)) { continue; } return candidate; } // No appropriate IME/subtype is found in the list. Give up. return null; } // The current IME/subtype is not found in the list. Give up. } @VisibleForTesting public static class ControllerImpl { private final StaticRotationList mSwitchingAwareSubtypeList; private final StaticRotationList mSwitchingUnawareSubtypeList; public ControllerImpl(final List<ImeSubtypeListItem> sortedItems) { mSwitchingAwareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, true /* supportsSwitchingToNextInputMethod */)); mSwitchingUnawareSubtypeList = new StaticRotationList(filterImeSubtypeList(sortedItems, false /* supportsSwitchingToNextInputMethod */)); } public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { if (imi == null) { return null; } if (imi.supportsSwitchingToNextInputMethod()) { return mSwitchingAwareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, subtype); } else { return mSwitchingUnawareSubtypeList.getNextInputMethodLocked(onlyCurrentIme, imi, subtype); } } private static List<ImeSubtypeListItem> filterImeSubtypeList( final List<ImeSubtypeListItem> items, final boolean supportsSwitchingToNextInputMethod) { final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); final int ALL_ITEMS_COUNT = items.size(); for (int i = 0; i < ALL_ITEMS_COUNT; i++) { final ImeSubtypeListItem item = items.get(i); if (item.mImi.supportsSwitchingToNextInputMethod() == supportsSwitchingToNextInputMethod) { result.add(item); } } return result; } } private final InputMethodSettings mSettings; private InputMethodAndSubtypeList mSubtypeList; private ControllerImpl mController; private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) { mSettings = settings; Loading @@ -276,12 +334,18 @@ public class InputMethodSubtypeSwitchingController { public void resetCircularListLocked(Context context) { mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); mController = new ControllerImpl(mSubtypeList.getSortedInputMethodAndSubtypeList()); } public ImeSubtypeListItem getNextInputMethodLocked( boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { return getNextInputMethodLockedImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(), onlyCurrentIme, imi, subtype); public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { if (mController == null) { if (DEBUG) { Log.e(TAG, "mController shouldn't be null."); } return null; } return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, Loading
core/tests/inputmethodtests/src/android/os/InputMethodSubtypeSwitchingControllerTest.java +120 −119 Original line number Diff line number Diff line Loading @@ -25,8 +25,9 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ControllerImpl; import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; import com.android.internal.inputmethod.InputMethodUtils; import java.util.ArrayList; import java.util.Arrays; Loading @@ -39,6 +40,7 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe private static final boolean DUMMY_FORCE_DEFAULT = false; private static final int DUMMY_IS_DEFAULT_RES_ID = 0; private static final String SYSTEM_LOCALE = "en_US"; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static InputMethodSubtype createDummySubtype(final String locale) { final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); Loading @@ -64,142 +66,141 @@ public class InputMethodSubtypeSwitchingControllerTest extends InstrumentationTe si.exported = true; si.nonLocalizedLabel = imeLabel; ri.serviceInfo = si; final List<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); List<InputMethodSubtype> subtypes = null; if (subtypeLocales != null) { subtypes = new ArrayList<InputMethodSubtype>(); for (String subtypeLocale : subtypeLocales) { subtypes.add(createDummySubtype(subtypeLocale)); } } final InputMethodInfo imi = new InputMethodInfo(ri, DUMMY_IS_AUX_IME, DUMMY_SETTING_ACTIVITY_NAME, subtypes, DUMMY_IS_DEFAULT_RES_ID, DUMMY_FORCE_DEFAULT, supportsSwitchingToNextInputMethod); if (subtypes == null) { items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, NOT_A_SUBTYPE_ID, null, SYSTEM_LOCALE)); } else { for (int i = 0; i < subtypes.size(); ++i) { final String subtypeLocale = subtypeLocales.get(i); items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, SYSTEM_LOCALE)); } } } private static List<ImeSubtypeListItem> createTestData() { private static List<ImeSubtypeListItem> createEnabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); addDummyImeSubtypeListItems(items, "switchAwareLatinIme", "switchAwareLatinIme", Arrays.asList("en_US", "es_US", "fr"), addDummyImeSubtypeListItems(items, "LatinIme", "LatinIme", Arrays.asList("en_US", "fr"), true /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "nonSwitchAwareLatinIme", "nonSwitchAwareLatinIme", addDummyImeSubtypeListItems(items, "switchUnawareLatinIme", "switchUnawareLatinIme", Arrays.asList("en_UK", "hi"), false /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "switchAwareJapaneseIme", "switchAwareJapaneseIme", Arrays.asList("ja_JP"), addDummyImeSubtypeListItems(items, "subtypeUnawareIme", "subtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "JapaneseIme", "JapaneseIme", Arrays.asList("ja_JP"), true /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "nonSwitchAwareJapaneseIme", "nonSwitchAwareJapaneseIme", Arrays.asList("ja_JP"), addDummyImeSubtypeListItems(items, "switchUnawareJapaneseIme", "switchUnawareJapaneseIme", Arrays.asList("ja_JP"), false /* supportsSwitchingToNextInputMethod*/); return items; } private static List<ImeSubtypeListItem> createDisabledImeSubtypes() { final List<ImeSubtypeListItem> items = new ArrayList<ImeSubtypeListItem>(); addDummyImeSubtypeListItems(items, "UnknownIme", "UnknownIme", Arrays.asList("en_US", "hi"), true /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "UnknownSwitchingUnawareIme", "UnknownSwitchingUnawareIme", Arrays.asList("en_US"), false /* supportsSwitchingToNextInputMethod*/); addDummyImeSubtypeListItems(items, "UnknownSubtypeUnawareIme", "UnknownSubtypeUnawareIme", null, false /* supportsSwitchingToNextInputMethod*/); return items; } @SmallTest public void testGetNextInputMethodImplWithNotOnlyCurrentIme() throws Exception { final List<ImeSubtypeListItem> imList = createTestData(); final boolean ONLY_CURRENT_IME = false; ImeSubtypeListItem currentIme; ImeSubtypeListItem nextIme; // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" currentIme = imList.get(0); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(1), nextIme); // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" currentIme = imList.get(1); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(2), nextIme); // "switchAwareLatinIme/fr" -> "switchAwareJapaneseIme/ja_JP" currentIme = imList.get(2); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(5), nextIme); // "switchAwareJapaneseIme/ja_JP" -> "switchAwareLatinIme/en_US" currentIme = imList.get(5); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(0), nextIme); // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" currentIme = imList.get(3); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(4), nextIme); // "nonSwitchAwareLatinIme/hi" -> "nonSwitchAwareJapaneseIme/ja_JP" currentIme = imList.get(4); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(6), nextIme); // "nonSwitchAwareJapaneseIme/ja_JP" -> "nonSwitchAwareLatinIme/en_UK" currentIme = imList.get(6); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(3), nextIme); private void assertNextInputMethod(final ControllerImpl controller, final boolean onlyCurrentIme, final ImeSubtypeListItem currentItem, final ImeSubtypeListItem nextItem) { InputMethodSubtype subtype = null; if (currentItem.mSubtypeName != null) { subtype = createDummySubtype(currentItem.mSubtypeName.toString()); } final ImeSubtypeListItem nextIme = controller.getNextInputMethod(onlyCurrentIme, currentItem.mImi, subtype); assertEquals(nextItem, nextIme); } @SmallTest public void testGetNextInputMethodImplWithOnlyCurrentIme() throws Exception { final List<ImeSubtypeListItem> imList = createTestData(); final boolean ONLY_CURRENT_IME = true; ImeSubtypeListItem currentIme; ImeSubtypeListItem nextIme; // "switchAwareLatinIme/en_US" -> "switchAwareLatinIme/es_US" currentIme = imList.get(0); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(1), nextIme); // "switchAwareLatinIme/es_US" -> "switchAwareLatinIme/fr" currentIme = imList.get(1); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(2), nextIme); // "switchAwareLatinIme/fr" -> "switchAwareLatinIme/en_US" currentIme = imList.get(2); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(0), nextIme); // "nonSwitchAwareLatinIme/en_UK" -> "nonSwitchAwareLatinIme/hi" currentIme = imList.get(3); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(4), nextIme); // "nonSwitchAwareLatinIme/hi" -> "switchAwareLatinIme/en_UK" currentIme = imList.get(4); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertEquals(imList.get(3), nextIme); // "switchAwareJapaneseIme/ja_JP" -> null currentIme = imList.get(5); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertNull(nextIme); // "nonSwitchAwareJapaneseIme/ja_JP" -> null currentIme = imList.get(6); nextIme = InputMethodSubtypeSwitchingController.getNextInputMethodLockedImpl( imList, ONLY_CURRENT_IME, currentIme.mImi, createDummySubtype( currentIme.mSubtypeName.toString())); assertNull(nextIme); public void testControllerImpl() throws Exception { final List<ImeSubtypeListItem> disabledItems = createDisabledImeSubtypes(); final ImeSubtypeListItem disabledIme_en_US = disabledItems.get(0); final ImeSubtypeListItem disabledIme_hi = disabledItems.get(1); final ImeSubtypeListItem disabledSwitchingUnawareIme = disabledItems.get(2); final ImeSubtypeListItem disabledSubtypeUnawareIme = disabledItems.get(3); final List<ImeSubtypeListItem> enabledItems = createEnabledImeSubtypes(); final ImeSubtypeListItem latinIme_en_US = enabledItems.get(0); final ImeSubtypeListItem latinIme_fr = enabledItems.get(1); final ImeSubtypeListItem switchingUnawarelatinIme_en_UK = enabledItems.get(2); final ImeSubtypeListItem switchingUnawarelatinIme_hi = enabledItems.get(3); final ImeSubtypeListItem subtypeUnawareIme = enabledItems.get(4); final ImeSubtypeListItem japaneseIme_ja_JP = enabledItems.get(5); final ImeSubtypeListItem switchUnawareJapaneseIme_ja_JP = enabledItems.get(6); final ControllerImpl controller = new ControllerImpl(enabledItems); // switching-aware loop assertNextInputMethod(controller, false /* onlyCurrentIme */, latinIme_en_US, latinIme_fr); assertNextInputMethod(controller, false /* onlyCurrentIme */, latinIme_fr, japaneseIme_ja_JP); assertNextInputMethod(controller, false /* onlyCurrentIme */, japaneseIme_ja_JP, latinIme_en_US); // switching-unaware loop assertNextInputMethod(controller, false /* onlyCurrentIme */, switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); assertNextInputMethod(controller, false /* onlyCurrentIme */, switchingUnawarelatinIme_hi, subtypeUnawareIme); assertNextInputMethod(controller, false /* onlyCurrentIme */, subtypeUnawareIme, switchUnawareJapaneseIme_ja_JP); assertNextInputMethod(controller, false /* onlyCurrentIme */, switchUnawareJapaneseIme_ja_JP, switchingUnawarelatinIme_en_UK); // test onlyCurrentIme == true assertNextInputMethod(controller, true /* onlyCurrentIme */, latinIme_en_US, latinIme_fr); assertNextInputMethod(controller, true /* onlyCurrentIme */, latinIme_fr, latinIme_en_US); assertNextInputMethod(controller, true /* onlyCurrentIme */, switchingUnawarelatinIme_en_UK, switchingUnawarelatinIme_hi); assertNextInputMethod(controller, true /* onlyCurrentIme */, switchingUnawarelatinIme_hi, switchingUnawarelatinIme_en_UK); assertNextInputMethod(controller, true /* onlyCurrentIme */, subtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, japaneseIme_ja_JP, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, switchUnawareJapaneseIme_ja_JP, null); // Make sure that disabled IMEs are not accepted. assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledIme_en_US, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledIme_hi, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledSwitchingUnawareIme, null); assertNextInputMethod(controller, false /* onlyCurrentIme */, disabledSubtypeUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledIme_en_US, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledIme_hi, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledSwitchingUnawareIme, null); assertNextInputMethod(controller, true /* onlyCurrentIme */, disabledSubtypeUnawareIme, null); } }