Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6c8ca847 authored by Yohei Yukawa's avatar Yohei Yukawa Committed by Android (Google) Code Review
Browse files

Merge "Consolidate the language-switching logic"

parents 7c28c366 9b29d045
Loading
Loading
Loading
Loading
+102 −38
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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,
+120 −119
Original line number Diff line number Diff line
@@ -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;
@@ -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();
@@ -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);
    }
 }