| /* |
| * Copyright (C) 2015 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 android.view.textservice; |
| |
| import static android.test.MoreAsserts.assertNotEqual; |
| |
| import android.os.Parcel; |
| import android.test.InstrumentationTestCase; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import java.util.Arrays; |
| import java.util.Locale; |
| |
| /** |
| * TODO: Most of part can be, and probably should be, moved to CTS. |
| */ |
| public class SpellCheckerSubtypeTest extends InstrumentationTestCase { |
| private static final int SUBTYPE_SUBTYPE_ID_NONE = 0; |
| private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_NONE = ""; |
| private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE = ""; |
| |
| private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_A = "en_GB"; |
| private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_A = "en-GB"; |
| private static final int SUBTYPE_NAME_RES_ID_A = 0x12345; |
| private static final String SUBTYPE_EXTRA_VALUE_A = "Key1=Value1,Key2=Value2"; |
| private static final int SUBTYPE_SUBTYPE_ID_A = 42; |
| private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_B = "en_IN"; |
| private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_B = "en-IN"; |
| private static final int SUBTYPE_NAME_RES_ID_B = 0x54321; |
| private static final String SUBTYPE_EXTRA_VALUE_B = "Key3=Value3,Key4=Value4"; |
| private static final int SUBTYPE_SUBTYPE_ID_B = -42; |
| |
| private static int defaultHashCodeAlgorithm(String locale, String extraValue) { |
| return Arrays.hashCode(new Object[] {locale, extraValue}); |
| } |
| |
| private static final SpellCheckerSubtype cloneViaParcel(final SpellCheckerSubtype original) { |
| Parcel parcel = null; |
| try { |
| parcel = Parcel.obtain(); |
| original.writeToParcel(parcel, 0); |
| parcel.setDataPosition(0); |
| return SpellCheckerSubtype.CREATOR.createFromParcel(parcel); |
| } finally { |
| if (parcel != null) { |
| parcel.recycle(); |
| } |
| } |
| } |
| |
| @SmallTest |
| public void testSubtypeWithNoSubtypeId() throws Exception { |
| final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, |
| SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, |
| SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE); |
| assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId()); |
| assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale()); |
| assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag()); |
| assertEquals("Value1", subtype.getExtraValueOf("Key1")); |
| assertEquals("Value2", subtype.getExtraValueOf("Key2")); |
| // Historically we have used SpellCheckerSubtype#hashCode() to track which subtype is |
| // enabled, and it is supposed to be stored in SecureSettings. Therefore we have to |
| // keep using the same algorithm for compatibility reasons. |
| assertEquals( |
| defaultHashCodeAlgorithm(SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A), |
| subtype.hashCode()); |
| |
| final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype); |
| assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId()); |
| assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale()); |
| assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag()); |
| assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1")); |
| assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2")); |
| assertEquals( |
| defaultHashCodeAlgorithm(SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A), |
| clonedSubtype.hashCode()); |
| } |
| |
| public void testSubtypeWithSubtypeId() throws Exception { |
| final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, |
| SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, |
| SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A); |
| |
| assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId()); |
| assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale()); |
| assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag()); |
| assertEquals("Value1", subtype.getExtraValueOf("Key1")); |
| assertEquals("Value2", subtype.getExtraValueOf("Key2")); |
| // Similar to "SubtypeId" in InputMethodSubtype, "SubtypeId" in SpellCheckerSubtype enables |
| // developers to specify a stable and consistent ID for each subtype. |
| assertEquals(SUBTYPE_SUBTYPE_ID_A, subtype.hashCode()); |
| |
| final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype); |
| assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId()); |
| assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale()); |
| assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag()); |
| assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1")); |
| assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2")); |
| assertEquals(SUBTYPE_SUBTYPE_ID_A, clonedSubtype.hashCode()); |
| } |
| |
| @SmallTest |
| public void testGetLocaleObject() throws Exception { |
| assertEquals(new Locale("en", "GB"), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_GB", |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); |
| assertEquals(new Locale("en", "GB"), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE, |
| "en-GB", SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); |
| |
| // If neither locale string nor language tag is specified, |
| // {@link SpellCheckerSubtype#getLocaleObject} returns null. |
| assertNull( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); |
| |
| // If both locale string and language tag are specified, |
| // {@link SpellCheckerSubtype#getLocaleObject} uses language tag. |
| assertEquals(new Locale("en", "GB"), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_US", "en-GB", |
| SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); |
| |
| // Make sure that "tl_PH" is rewritten to "fil_PH" for spell checkers that need to support |
| // Android KitKat and prior, which do not support 3-letter language codes. |
| assertEquals(new Locale("fil", "PH"), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "tl_PH", |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject()); |
| |
| // "languageTag" attribute is available in Android N and later, where 3-letter country codes |
| // are guaranteed to be available. It's developers' responsibility for specifying a valid |
| // country subtags here and we do not rewrite "tl" to "fil" for simplicity. |
| assertEquals("tl", |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE, |
| "tl-PH", SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE) |
| .getLocaleObject().getLanguage()); |
| } |
| |
| @SmallTest |
| public void testEquality() throws Exception { |
| assertEquals( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_EXTRA_VALUE_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_EXTRA_VALUE_A)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_EXTRA_VALUE_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B, |
| SUBTYPE_EXTRA_VALUE_A)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_EXTRA_VALUE_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B, |
| SUBTYPE_EXTRA_VALUE_A)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_EXTRA_VALUE_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_EXTRA_VALUE_B)); |
| |
| // If subtype ID is 0 (== SUBTYPE_SUBTYPE_ID_NONE), we keep the same behavior. |
| assertEquals( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_NONE), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B, |
| SUBTYPE_SUBTYPE_ID_NONE)); |
| |
| // If subtype ID is not 0, we test the equality based only on the subtype ID. |
| assertEquals( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A)); |
| assertEquals( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A)); |
| assertEquals( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A)); |
| assertEquals( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A)); |
| assertEquals( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B, |
| SUBTYPE_SUBTYPE_ID_A)); |
| assertNotEqual( |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_A), |
| new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A, |
| SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A, |
| SUBTYPE_SUBTYPE_ID_B)); |
| } |
| |
| } |