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

Commit 08a3ecea authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Remove duplicate code from InputMethodUtils

This is a follow up CL to my CL [1], which originally aimed to revert
the CL [2] that added several methods into InputMethodUtils but could
not because those methods were already used in other places.

Interestingly we already have almost the same code in
InputMethodAndSubtypeUtil in SettingsLib package on which those other
projects can depend on. Hence this CL removes InputMethodUtils' ones
to avoid code duplication.

Although in future we may move the logic back to framework internal,
at the moment it is more important to reduce code duplication.

Note that this CL also ports existing test cases for those two
functions from FrameworksCoreTests to RunSettingsLibRoboTests in
GoogleTruth style.

 [1]: I122a8f69b2f75a9af85e14b66db764c5d153040e
      ca780950
 [2]: I01f5fafbbcfe3e3f5313829162ec011eaf2ad991
      2028ddaa

Fixes: 73056657
Test: atest InputMethodUtilsTest
Test: make RunSettingsLibRoboTests -j
Test: prebuilts/checkstyle/checkstyle.py -f frameworks/base/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilTest.java
Change-Id: Iad926ffd9806482a5aef0d8c20d2049be40d03c9
parent a3a17d9c
Loading
Loading
Loading
Loading
+1 −71
Original line number Diff line number Diff line
@@ -34,10 +34,6 @@ import android.os.LocaleList;
import android.os.RemoteException;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.Printer;
import android.util.Slog;
@@ -783,58 +779,6 @@ public class InputMethodUtils {
        }
    }

    /**
     * Parses the setting stored input methods and subtypes string value.
     *
     * @param inputMethodsAndSubtypesString The input method subtypes value stored in settings.
     * @return Map from input method ID to set of input method subtypes IDs.
     */
    @VisibleForTesting
    public static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString(
            @Nullable final String inputMethodsAndSubtypesString) {

        final ArrayMap<String, ArraySet<String>> imeMap = new ArrayMap<>();
        if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
            return imeMap;
        }

        final SimpleStringSplitter typeSplitter =
                new SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
        final SimpleStringSplitter subtypeSplitter =
                new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);

        List<Pair<String, ArrayList<String>>> allImeSettings =
                InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString,
                        typeSplitter,
                        subtypeSplitter);
        for (Pair<String, ArrayList<String>> ime : allImeSettings) {
            ArraySet<String> subtypes = new ArraySet<>();
            if (ime.second != null) {
                subtypes.addAll(ime.second);
            }
            imeMap.put(ime.first, subtypes);
        }
        return imeMap;
    }

    @NonNull
    public static String buildInputMethodsAndSubtypesString(
            @NonNull final ArrayMap<String, ArraySet<String>> map) {
        // we want to use the canonical InputMethodSettings implementation,
        // so we convert data structures first.
        List<Pair<String, ArrayList<String>>> imeMap = new ArrayList<>(4);
        for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) {
            final String imeName = entry.getKey();
            final ArraySet<String> subtypeSet = entry.getValue();
            final ArrayList<String> subtypes = new ArrayList<>(2);
            if (subtypeSet != null) {
                subtypes.addAll(subtypeSet);
            }
            imeMap.add(new Pair<>(imeName, subtypes));
        }
        return InputMethodSettings.buildInputMethodsSettingString(imeMap);
    }

    /**
     * Utility class for putting and getting settings for InputMethod
     * TODO: Move all putters and getters of settings to this class.
@@ -872,21 +816,7 @@ public class InputMethodUtils {
            }
        }

        public static String buildInputMethodsSettingString(
                List<Pair<String, ArrayList<String>>> allImeSettingsMap) {
            final StringBuilder b = new StringBuilder();
            boolean needsSeparator = false;
            for (Pair<String, ArrayList<String>> ime : allImeSettingsMap) {
                if (needsSeparator) {
                    b.append(INPUT_METHOD_SEPARATOR);
                }
                buildEnabledInputMethodsSettingString(b, ime);
                needsSeparator = true;
            }
            return b.toString();
        }

        public static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
        private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
                String enabledInputMethodsStr,
                TextUtils.SimpleStringSplitter inputMethodSplitter,
                TextUtils.SimpleStringSplitter subtypeSplitter) {
+0 −244
Original line number Diff line number Diff line
@@ -40,8 +40,6 @@ import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -1191,248 +1189,6 @@ public class InputMethodUtilsTest {
        }
    }

    @Test
    public void testParseInputMethodsAndSubtypesString() {
        // Trivial cases.
        {
            assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString("").isEmpty());
            assertTrue(InputMethodUtils.parseInputMethodsAndSubtypesString(null).isEmpty());
        }

        // No subtype cases.
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0");
            assertEquals(1, r.size());
            assertTrue(r.containsKey("ime0"));
            assertTrue(r.get("ime0").isEmpty());
        }
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0:ime1");
            assertEquals(2, r.size());
            assertTrue(r.containsKey("ime0"));
            assertTrue(r.get("ime0").isEmpty());
            assertTrue(r.containsKey("ime1"));
            assertTrue(r.get("ime1").isEmpty());
        }

        // Input metho IDs and their subtypes.
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0");
            assertEquals(1, r.size());
            assertTrue(r.containsKey("ime0"));
            ArraySet<String> subtypes = r.get("ime0");
            assertEquals(1, subtypes.size());
            assertTrue(subtypes.contains("subtype0"));
        }
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype0");
            assertEquals(1, r.size());
            assertTrue(r.containsKey("ime0"));
            ArraySet<String> subtypes = r.get("ime0");
            assertEquals(1, subtypes.size());
            assertTrue(subtypes.contains("subtype0"));
        }
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString("ime0;subtype0;subtype1");
            assertEquals(1, r.size());
            assertTrue(r.containsKey("ime0"));
            ArraySet<String> subtypes = r.get("ime0");
            assertEquals(2, subtypes.size());
            assertTrue(subtypes.contains("subtype0"));
            assertTrue(subtypes.contains("subtype1"));
        }
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString(
                            "ime0;subtype0:ime1;subtype1");
            assertEquals(2, r.size());
            assertTrue(r.containsKey("ime0"));
            assertTrue(r.containsKey("ime1"));
            ArraySet<String> subtypes0 = r.get("ime0");
            assertEquals(1, subtypes0.size());
            assertTrue(subtypes0.contains("subtype0"));

            ArraySet<String> subtypes1 = r.get("ime1");
            assertEquals(1, subtypes1.size());
            assertTrue(subtypes1.contains("subtype1"));
        }
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString(
                            "ime0;subtype0;subtype1:ime1;subtype2");
            assertEquals(2, r.size());
            assertTrue(r.containsKey("ime0"));
            assertTrue(r.containsKey("ime1"));
            ArraySet<String> subtypes0 = r.get("ime0");
            assertEquals(2, subtypes0.size());
            assertTrue(subtypes0.contains("subtype0"));
            assertTrue(subtypes0.contains("subtype1"));

            ArraySet<String> subtypes1 = r.get("ime1");
            assertEquals(1, subtypes1.size());
            assertTrue(subtypes1.contains("subtype2"));
        }
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString(
                            "ime0;subtype0;subtype1:ime1;subtype1;subtype2");
            assertEquals(2, r.size());
            assertTrue(r.containsKey("ime0"));
            assertTrue(r.containsKey("ime1"));
            ArraySet<String> subtypes0 = r.get("ime0");
            assertEquals(2, subtypes0.size());
            assertTrue(subtypes0.contains("subtype0"));
            assertTrue(subtypes0.contains("subtype1"));

            ArraySet<String> subtypes1 = r.get("ime1");
            assertEquals(2, subtypes1.size());
            assertTrue(subtypes0.contains("subtype1"));
            assertTrue(subtypes1.contains("subtype2"));
        }
        {
            ArrayMap<String, ArraySet<String>> r =
                    InputMethodUtils.parseInputMethodsAndSubtypesString(
                            "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
            assertEquals(3, r.size());
            assertTrue(r.containsKey("ime0"));
            assertTrue(r.containsKey("ime1"));
            assertTrue(r.containsKey("ime2"));
            ArraySet<String> subtypes0 = r.get("ime0");
            assertEquals(2, subtypes0.size());
            assertTrue(subtypes0.contains("subtype0"));
            assertTrue(subtypes0.contains("subtype1"));

            ArraySet<String> subtypes1 = r.get("ime1");
            assertEquals(2, subtypes1.size());
            assertTrue(subtypes0.contains("subtype1"));
            assertTrue(subtypes1.contains("subtype2"));

            ArraySet<String> subtypes2 = r.get("ime2");
            assertTrue(subtypes2.isEmpty());
        }
    }

    @Test
    public void testbuildInputMethodsAndSubtypesString() {
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            assertEquals("", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            map.put("ime0", new ArraySet<>());
            assertEquals("ime0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            ArraySet<String> subtypes1 = new ArraySet<>();
            subtypes1.add("subtype0");
            map.put("ime0", subtypes1);
            assertEquals("ime0;subtype0", InputMethodUtils.buildInputMethodsAndSubtypesString(map));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            ArraySet<String> subtypes1 = new ArraySet<>();
            subtypes1.add("subtype0");
            subtypes1.add("subtype1");
            map.put("ime0", subtypes1);

            // We do not expect what order will be used to concatenate items in
            // InputMethodUtils.buildInputMethodsAndSubtypesString() hence enumerate all possible
            // permutations here.
            ArraySet<String> validSequences = new ArraySet<>();
            validSequences.add("ime0;subtype0;subtype1");
            validSequences.add("ime0;subtype1;subtype0");
            assertTrue(validSequences.contains(
                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            map.put("ime0", new ArraySet<>());
            map.put("ime1", new ArraySet<>());

            ArraySet<String> validSequences = new ArraySet<>();
            validSequences.add("ime0:ime1");
            validSequences.add("ime1:ime0");
            assertTrue(validSequences.contains(
                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            ArraySet<String> subtypes1 = new ArraySet<>();
            subtypes1.add("subtype0");
            map.put("ime0", subtypes1);
            map.put("ime1", new ArraySet<>());

            ArraySet<String> validSequences = new ArraySet<>();
            validSequences.add("ime0;subtype0:ime1");
            validSequences.add("ime1;ime0;subtype0");
            assertTrue(validSequences.contains(
                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            ArraySet<String> subtypes1 = new ArraySet<>();
            subtypes1.add("subtype0");
            subtypes1.add("subtype1");
            map.put("ime0", subtypes1);
            map.put("ime1", new ArraySet<>());

            ArraySet<String> validSequences = new ArraySet<>();
            validSequences.add("ime0;subtype0;subtype1:ime1");
            validSequences.add("ime0;subtype1;subtype0:ime1");
            validSequences.add("ime1:ime0;subtype0;subtype1");
            validSequences.add("ime1:ime0;subtype1;subtype0");
            assertTrue(validSequences.contains(
                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            ArraySet<String> subtypes1 = new ArraySet<>();
            subtypes1.add("subtype0");
            map.put("ime0", subtypes1);

            ArraySet<String> subtypes2 = new ArraySet<>();
            subtypes2.add("subtype1");
            map.put("ime1", subtypes2);

            ArraySet<String> validSequences = new ArraySet<>();
            validSequences.add("ime0;subtype0:ime1;subtype1");
            validSequences.add("ime1;subtype1:ime0;subtype0");
            assertTrue(validSequences.contains(
                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
        }
        {
            ArrayMap<String, ArraySet<String>> map = new ArrayMap<>();
            ArraySet<String> subtypes1 = new ArraySet<>();
            subtypes1.add("subtype0");
            subtypes1.add("subtype1");
            map.put("ime0", subtypes1);

            ArraySet<String> subtypes2 = new ArraySet<>();
            subtypes2.add("subtype2");
            subtypes2.add("subtype3");
            map.put("ime1", subtypes2);

            ArraySet<String> validSequences = new ArraySet<>();
            validSequences.add("ime0;subtype0;subtype1:ime1;subtype2;subtype3");
            validSequences.add("ime0;subtype1;subtype0:ime1;subtype2;subtype3");
            validSequences.add("ime0;subtype0;subtype1:ime1;subtype3;subtype2");
            validSequences.add("ime0;subtype1;subtype0:ime1;subtype3;subtype2");
            validSequences.add("ime1;subtype2;subtype3:ime0;subtype0;subtype1");
            validSequences.add("ime2;subtype3;subtype2:ime0;subtype0;subtype1");
            validSequences.add("ime3;subtype2;subtype3:ime0;subtype1;subtype0");
            validSequences.add("ime4;subtype3;subtype2:ime0;subtype1;subtype0");
            assertTrue(validSequences.contains(
                    InputMethodUtils.buildInputMethodsAndSubtypesString(map)));
        }
    }

    @Test
    public void testConstructLocaleFromString() throws Exception {
        assertEquals(new Locale("en"), InputMethodUtils.constructLocaleFromString("en"));
+2 −2
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ public class InputMethodAndSubtypeUtil {

    // InputMethods and subtypes are saved in the settings as follows:
    // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
    private static String buildInputMethodsAndSubtypesString(
    public static String buildInputMethodsAndSubtypesString(
            final HashMap<String, HashSet<String>> imeToSubtypesMap) {
        final StringBuilder builder = new StringBuilder();
        for (final String imi : imeToSubtypesMap.keySet()) {
@@ -116,7 +116,7 @@ public class InputMethodAndSubtypeUtil {
        return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
    }

    private static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
    public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
            final String inputMethodsAndSubtypesString) {
        final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
        if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
+190 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.inputmethod;

import static com.google.common.truth.Truth.assertThat;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

import java.util.HashMap;
import java.util.HashSet;

@RunWith(RobolectricTestRunner.class)
public class InputMethodAndSubtypeUtilTest {

    private static final HashSet<String> EMPTY_STRING_SET = new HashSet<>();

    private static HashSet<String> asHashSet(String... strings) {
        HashSet<String> hashSet = new HashSet<>();
        for (String s : strings) {
            hashSet.add(s);
        }
        return hashSet;
    }

    @Test
    public void testParseInputMethodsAndSubtypesString_EmptyString() {
        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("")).isEmpty();
        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(null)).isEmpty();
    }

    @Test
    public void testParseInputMethodsAndSubtypesString_SingleImeNoSubtype() {
        HashMap<String, HashSet<String>> r =
                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0");
        assertThat(r).containsExactly("ime0", EMPTY_STRING_SET);
    }

    @Test
    public void testParseInputMethodsAndSubtypesString_MultipleImesNoSubtype() {
        HashMap<String, HashSet<String>> r =
                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0:ime1");
        assertThat(r).containsExactly("ime0", EMPTY_STRING_SET, "ime1", EMPTY_STRING_SET);
    }

    @Test
    public void testParseInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
        HashMap<String, HashSet<String>> r =
                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString("ime0;subtype0");
        assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
    }

    @Test
    public void testParseInputMethodsAndSubtypesString_SingleImeDuplicateSameSubtypes() {
        HashMap<String, HashSet<String>> r =
                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
                        "ime0;subtype0;subtype0");
        assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
    }

    @Test
    public void testParseInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
        HashMap<String, HashSet<String>> r =
                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
                        "ime0;subtype0;subtype1");
        assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"));
    }

    @Test
    public void testParseInputMethodsAndSubtypesString_MultiplePairsOfImeSubtype() {
        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
                "ime0;subtype0:ime1;subtype1"))
                .containsExactly("ime0", asHashSet("subtype0"), "ime1", asHashSet("subtype1"));
        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
                "ime0;subtype0;subtype1:ime1;subtype2"))
                .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
                        "ime1", asHashSet("subtype2"));
        assertThat(InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
                "ime0;subtype0;subtype1:ime1;subtype1;subtype2"))
                .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
                        "ime1", asHashSet("subtype1", "subtype2"));

    }

    @Test
    public void testParseInputMethodsAndSubtypesString_MixedImeSubtypePairsAndImeNoSubtype() {
        HashMap<String, HashSet<String>> r =
                InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(
                        "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
        assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"),
                "ime1", asHashSet("subtype1", "subtype2"),
                "ime2", EMPTY_STRING_SET);
    }

    @Test
    public void testBuildInputMethodsAndSubtypesString_EmptyInput() {
        HashMap<String, HashSet<String>> map = new HashMap<>();
        assertThat(map).isEmpty();
    }

    @Test
    public void testBuildInputMethodsAndSubtypesString_SingleIme() {
        HashMap<String, HashSet<String>> map = new HashMap<>();
        map.put("ime0", new HashSet<>());
        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
        assertThat(result).isEqualTo("ime0");
    }

    @Test
    public void testBuildInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
        HashMap<String, HashSet<String>> map = new HashMap<>();
        map.put("ime0", asHashSet("subtype0"));
        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);
        assertThat(result).isEqualTo("ime0;subtype0");
    }

    @Test
    public void testBuildInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
        HashMap<String, HashSet<String>> map = new HashMap<>();
        map.put("ime0", asHashSet("subtype0", "subtype1"));
        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);

        // We do not expect what order will be used to concatenate items in
        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
        // permutations here.
        assertThat(result).matches("(ime0;subtype0;subtype1)|(ime0;subtype1;subtype0)");
    }

    @Test
    public void testBuildInputMethodsAndSubtypesString_MultipleImesNoSubtypes() {
        HashMap<String, HashSet<String>> map = new HashMap<>();
        map.put("ime0", EMPTY_STRING_SET);
        map.put("ime1", EMPTY_STRING_SET);
        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);

        // We do not expect what order will be used to concatenate items in
        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
        // permutations here.
        assertThat(result).matches("(ime0:ime1)|(ime1:ime0)");
    }

    @Test
    public void testBuildInputMethodsAndSubtypesString_MultipleImesWithAndWithoutSubtypes() {
        HashMap<String, HashSet<String>> map = new HashMap<>();
        map.put("ime0", asHashSet("subtype0", "subtype1"));
        map.put("ime1", EMPTY_STRING_SET);
        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);

        // We do not expect what order will be used to concatenate items in
        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
        // permutations here.
        assertThat(result).matches("(ime0;subtype0;subtype1:ime1)|(ime0;subtype1;subtype0:ime1)"
                + "|(ime1:ime0;subtype0;subtype1)|(ime1:ime0;subtype1;subtype0)");
    }

    @Test
    public void testBuildInputMethodsAndSubtypesString_MultipleImesWithSubtypes() {
        HashMap<String, HashSet<String>> map = new HashMap<>();
        map.put("ime0", asHashSet("subtype0", "subtype1"));
        map.put("ime1", asHashSet("subtype2", "subtype3"));
        String result = InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString(map);

        // We do not expect what order will be used to concatenate items in
        // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
        // permutations here.
        assertThat(result).matches("|(ime0;subtype0;subtype1:ime1;subtype2;subtype3)|"
                + "|(ime0;subtype1;subtype0:ime1;subtype2;subtype3)|"
                + "|(ime0;subtype0;subtype1:ime1;subtype3;subtype2)|"
                + "|(ime0;subtype1;subtype0:ime1;subtype3;subtype2)|"
                + "|(ime1;subtype2;subtype3:ime0;subtype0;subtype1)|"
                + "|(ime2;subtype3;subtype2:ime0;subtype0;subtype1)|"
                + "|(ime3;subtype2;subtype3:ime0;subtype1;subtype0)|"
                + "|(ime4;subtype3;subtype2:ime0;subtype1;subtype0)");
    }
}