From 6504675cccdab35eec9adf489554e5e0ec87b99b Mon Sep 17 00:00:00 2001 From: "Tadashi G. Takaoka" Date: Tue, 1 Aug 2017 13:02:59 +0900 Subject: [PATCH] Add test for InputMethodSubtypePreference Check that InputMethodSubtypePrefernce#compareTo(Preference,Collator) complies with Comparable contracts. Fixes: 29571417 Test: Pass SettingsLibTest. Install SettingsLibTest.apk and run test. $ adb shell am instrument -w \ -e class com.android.settingslib.inputmethod.InputMethodSubtypePreferenceTest \ com.android.settingslib/android.support.test.runner.AndroidJUnitRunner Change-Id: I96038a7b548469ee34a6f78c75c894f40cd4f338 --- .../InputMethodSubtypePreference.java | 40 +++-- .../InputMethodSubtypePreferenceTest.java | 139 ++++++++++++++++++ 2 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java index 5fdab2967d1e..f824ec75968b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java @@ -17,12 +17,12 @@ package com.android.settingslib.inputmethod; import android.content.Context; -import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.Preference; import android.text.TextUtils; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodUtils; import java.text.Collator; @@ -39,18 +39,28 @@ public class InputMethodSubtypePreference extends SwitchWithNoTextPreference { public InputMethodSubtypePreference(final Context context, final InputMethodSubtype subtype, final InputMethodInfo imi) { + this(context, + imi.getId() + subtype.hashCode(), + InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(subtype, context, imi), + subtype.getLocale(), + context.getResources().getConfiguration().locale); + } + + @VisibleForTesting + InputMethodSubtypePreference( + final Context context, + final String prefKey, + final CharSequence title, + final String subtypeLocaleString, + final Locale systemLocale) { super(context); setPersistent(false); - setKey(imi.getId() + subtype.hashCode()); - final CharSequence subtypeLabel = - InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(subtype, context, imi); - setTitle(subtypeLabel); - final String subtypeLocaleString = subtype.getLocale(); + setKey(prefKey); + setTitle(title); if (TextUtils.isEmpty(subtypeLocaleString)) { mIsSystemLocale = false; mIsSystemLanguage = false; } else { - final Locale systemLocale = context.getResources().getConfiguration().locale; mIsSystemLocale = subtypeLocaleString.equals(systemLocale.toString()); mIsSystemLanguage = mIsSystemLocale || InputMethodUtils.getLanguageFromLocaleString(subtypeLocaleString) @@ -76,15 +86,15 @@ public class InputMethodSubtypePreference extends SwitchWithNoTextPreference { if (!mIsSystemLanguage && rhsPref.mIsSystemLanguage) { return 1; } - final CharSequence t0 = getTitle(); - final CharSequence t1 = rhs.getTitle(); - if (t0 == null && t1 == null) { - return Integer.compare(hashCode(), rhs.hashCode()); + final CharSequence title = getTitle(); + final CharSequence rhsTitle = rhs.getTitle(); + final boolean emptyTitle = TextUtils.isEmpty(title); + final boolean rhsEmptyTitle = TextUtils.isEmpty(rhsTitle); + if (!emptyTitle && !rhsEmptyTitle) { + return collator.compare(title.toString(), rhsTitle.toString()); } - if (t0 != null && t1 != null) { - return collator.compare(t0.toString(), t1.toString()); - } - return t0 == null ? -1 : 1; + // For historical reasons, an empty text needs to be put at the first. + return (emptyTitle ? -1 : 0) - (rhsEmptyTitle ? -1 : 0); } return super.compareTo(rhs); } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java new file mode 100644 index 000000000000..8af027c024d0 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodSubtypePreferenceTest.java @@ -0,0 +1,139 @@ +package com.android.settingslib.inputmethod; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.function.BiConsumer; +import java.util.function.Function; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class InputMethodSubtypePreferenceTest { + + private static final List ITEMS_IN_ASCENDING = Arrays.asList( + // Subtypes that has the same locale of the system's. + createPreference("", "en_US", Locale.US), + createPreference("E", "en_US", Locale.US), + createPreference("Z", "en_US", Locale.US), + // Subtypes that has the same language of the system's. + createPreference("", "en", Locale.US), + createPreference("E", "en", Locale.US), + createPreference("Z", "en", Locale.US), + // Subtypes that has different language than the system's. + createPreference("", "ja", Locale.US), + createPreference("A", "hi_IN", Locale.US), + createPreference("B", "", Locale.US), + createPreference("E", "ja", Locale.US), + createPreference("Z", "ja", Locale.US) + ); + private static final List SAME_ITEMS = Arrays.asList( + // Subtypes that has different language than the system's. + createPreference("A", "ja_JP", Locale.US), + createPreference("A", "hi_IN", Locale.US), + // Subtypes that has an empty subtype locale string. + createPreference("A", "", Locale.US) + ); + private static final Collator COLLATOR = Collator.getInstance(Locale.US); + + @Test + public void testComparableOrdering() throws Exception { + onAllAdjacentItems(ITEMS_IN_ASCENDING, + (x, y) -> assertTrue( + x.getKey() + " is less than " + y.getKey(), + x.compareTo(y, COLLATOR) < 0) + ); + } + + @Test + public void testComparableEquality() { + onAllAdjacentItems(SAME_ITEMS, + (x, y) -> assertTrue( + x.getKey() + " is equal to " + y.getKey(), + x.compareTo(y, COLLATOR) == 0) + ); + } + + @Test + public void testComparableContracts() { + final Collection items = new ArrayList<>(); + items.addAll(ITEMS_IN_ASCENDING); + items.addAll(SAME_ITEMS); + items.add(createPreference("", "", Locale.US)); + items.add(createPreference("A", "en", Locale.US)); + items.add(createPreference("A", "en_US", Locale.US)); + items.add(createPreference("E", "hi_IN", Locale.US)); + items.add(createPreference("E", "en", Locale.US)); + items.add(createPreference("Z", "en_US", Locale.US)); + + assertComparableContracts( + items, + (x, y) -> x.compareTo(y, COLLATOR), + InputMethodSubtypePreference::getKey); + } + + private static InputMethodSubtypePreference createPreference( + final String subtypeName, + final String subtypeLocaleString, + final Locale systemLocale) { + return new InputMethodSubtypePreference( + InstrumentationRegistry.getTargetContext(), + subtypeName + "-" + subtypeLocaleString + "-" + systemLocale, + subtypeName, + subtypeLocaleString, + systemLocale); + } + + private static void onAllAdjacentItems(final List items, final BiConsumer check) { + for (int i = 0; i < items.size() - 1; i++) { + check.accept(items.get(i), items.get(i + 1)); + } + } + + @FunctionalInterface + interface CompareTo { + int apply(T t, T u); + } + + private static void assertComparableContracts(final Collection items, + final CompareTo compareTo, final Function name) { + for (final T x : items) { + final String nameX = name.apply(x); + assertTrue("Reflective: " + nameX + " is equal to itself", + compareTo.apply(x, x) == 0); + for (final T y : items) { + final String nameY = name.apply(y); + assertEquals("Asymmetric: " + nameX + " and " + nameY, + Integer.signum(compareTo.apply(x, y)), + -Integer.signum(compareTo.apply(y, x))); + for (final T z : items) { + final String nameZ = name.apply(z); + if (compareTo.apply(x, y) > 0 && compareTo.apply(y, z) > 0) { + assertTrue("Transitive: " + nameX + " is greater than " + nameY + + " and " + nameY + " is greater than " + nameZ + + " then " + nameX + " is greater than " + nameZ, + compareTo.apply(x, z) > 0); + } + if (compareTo.apply(x, y) == 0) { + assertEquals("Transitive: " + nameX + " and " + nameY + " is same " + + " then both return the same result for " + nameZ, + Integer.signum(compareTo.apply(x, z)), + Integer.signum(compareTo.apply(y, z))); + } + } + } + } + } +}