1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view.textservice;
18
19import android.os.Parcel;
20import android.test.InstrumentationTestCase;
21import android.test.suitebuilder.annotation.SmallTest;
22
23import java.util.Arrays;
24import java.util.Locale;
25
26import static android.test.MoreAsserts.assertNotEqual;
27
28/**
29 * TODO: Most of part can be, and probably should be, moved to CTS.
30 */
31public class SpellCheckerSubtypeTest extends InstrumentationTestCase {
32    private static final int SUBTYPE_SUBTYPE_ID_NONE = 0;
33    private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_NONE = "";
34    private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE = "";
35
36    private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_A = "en_GB";
37    private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_A = "en-GB";
38    private static final int SUBTYPE_NAME_RES_ID_A = 0x12345;
39    private static final String SUBTYPE_EXTRA_VALUE_A = "Key1=Value1,Key2=Value2";
40    private static final int SUBTYPE_SUBTYPE_ID_A = 42;
41    private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_B = "en_IN";
42    private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_B = "en-IN";
43    private static final int SUBTYPE_NAME_RES_ID_B = 0x54321;
44    private static final String SUBTYPE_EXTRA_VALUE_B = "Key3=Value3,Key4=Value4";
45    private static final int SUBTYPE_SUBTYPE_ID_B = -42;
46
47    private static int defaultHashCodeAlgorithm(String locale, String extraValue) {
48        return Arrays.hashCode(new Object[] {locale, extraValue});
49    }
50
51    private static final SpellCheckerSubtype cloneViaParcel(final SpellCheckerSubtype original) {
52        Parcel parcel = null;
53        try {
54            parcel = Parcel.obtain();
55            original.writeToParcel(parcel, 0);
56            parcel.setDataPosition(0);
57            return SpellCheckerSubtype.CREATOR.createFromParcel(parcel);
58        } finally {
59            if (parcel != null) {
60                parcel.recycle();
61            }
62        }
63    }
64
65    @SmallTest
66    public void testSubtypeWithNoSubtypeId() throws Exception {
67        final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
68                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A,
69                SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE);
70        assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
71        assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
72        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag());
73        assertEquals("Value1", subtype.getExtraValueOf("Key1"));
74        assertEquals("Value2", subtype.getExtraValueOf("Key2"));
75        // Historically we have used SpellCheckerSubtype#hashCode() to track which subtype is
76        // enabled, and it is supposed to be stored in SecureSettings.  Therefore we have to
77        // keep using the same algorithm for compatibility reasons.
78        assertEquals(
79                defaultHashCodeAlgorithm(SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A),
80                subtype.hashCode());
81
82        final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
83        assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
84        assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
85        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag());
86        assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
87        assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
88        assertEquals(
89                defaultHashCodeAlgorithm(SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A),
90                clonedSubtype.hashCode());
91    }
92
93    public void testSubtypeWithSubtypeId() throws Exception {
94        final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
95                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A,
96                SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A);
97
98        assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
99        assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
100        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag());
101        assertEquals("Value1", subtype.getExtraValueOf("Key1"));
102        assertEquals("Value2", subtype.getExtraValueOf("Key2"));
103        // Similar to "SubtypeId" in InputMethodSubtype, "SubtypeId" in SpellCheckerSubtype enables
104        // developers to specify a stable and consistent ID for each subtype.
105        assertEquals(SUBTYPE_SUBTYPE_ID_A, subtype.hashCode());
106
107        final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
108        assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
109        assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
110        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag());
111        assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
112        assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
113        assertEquals(SUBTYPE_SUBTYPE_ID_A, clonedSubtype.hashCode());
114    }
115
116    @SmallTest
117    public void testGetLocaleObject() throws Exception {
118        assertEquals(new Locale("en", "GB"),
119                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_GB",
120                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
121                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
122        assertEquals(new Locale("en", "GB"),
123                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
124                        "en-GB", SUBTYPE_EXTRA_VALUE_A,
125                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
126
127        // If neither locale string nor language tag is specified,
128        // {@link SpellCheckerSubtype#getLocaleObject} returns null.
129        assertNull(
130                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
131                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
132                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
133
134        // If both locale string and language tag are specified,
135        // {@link SpellCheckerSubtype#getLocaleObject} uses language tag.
136        assertEquals(new Locale("en", "GB"),
137                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_US", "en-GB",
138                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
139
140        // Make sure that "tl_PH" is rewritten to "fil_PH" for spell checkers that need to support
141        // Android KitKat and prior, which do not support 3-letter language codes.
142        assertEquals(new Locale("fil", "PH"),
143                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "tl_PH",
144                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
145                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
146
147        // "languageTag" attribute is available in Android N and later, where 3-letter country codes
148        // are guaranteed to be available.  It's developers' responsibility for specifying a valid
149        // country subtags here and we do not rewrite "tl" to "fil" for simplicity.
150        assertEquals("tl",
151                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
152                        "tl-PH", SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE)
153                        .getLocaleObject().getLanguage());
154    }
155
156    @SmallTest
157    public void testEquality() throws Exception {
158        assertEquals(
159                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
160                        SUBTYPE_EXTRA_VALUE_A),
161                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
162                        SUBTYPE_EXTRA_VALUE_A));
163        assertNotEqual(
164                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
165                        SUBTYPE_EXTRA_VALUE_A),
166                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
167                        SUBTYPE_EXTRA_VALUE_A));
168        assertNotEqual(
169                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
170                        SUBTYPE_EXTRA_VALUE_A),
171                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
172                        SUBTYPE_EXTRA_VALUE_A));
173        assertNotEqual(
174                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
175                        SUBTYPE_EXTRA_VALUE_A),
176                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
177                        SUBTYPE_EXTRA_VALUE_B));
178
179        // If subtype ID is 0 (== SUBTYPE_SUBTYPE_ID_NONE), we keep the same behavior.
180        assertEquals(
181                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
182                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
183                        SUBTYPE_SUBTYPE_ID_NONE),
184                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
185                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
186                        SUBTYPE_SUBTYPE_ID_NONE));
187        assertNotEqual(
188                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
189                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
190                        SUBTYPE_SUBTYPE_ID_NONE),
191                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
192                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
193                        SUBTYPE_SUBTYPE_ID_NONE));
194        assertNotEqual(
195                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
196                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
197                        SUBTYPE_SUBTYPE_ID_NONE),
198                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
199                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
200                        SUBTYPE_SUBTYPE_ID_NONE));
201        assertNotEqual(
202                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
203                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
204                        SUBTYPE_SUBTYPE_ID_NONE),
205                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
206                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A,
207                        SUBTYPE_SUBTYPE_ID_NONE));
208        assertNotEqual(
209                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
210                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
211                        SUBTYPE_SUBTYPE_ID_NONE),
212                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
213                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B,
214                        SUBTYPE_SUBTYPE_ID_NONE));
215
216        // If subtype ID is not 0, we test the equality based only on the subtype ID.
217        assertEquals(
218                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
219                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
220                        SUBTYPE_SUBTYPE_ID_A),
221                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
222                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
223                        SUBTYPE_SUBTYPE_ID_A));
224        assertEquals(
225                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
226                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
227                        SUBTYPE_SUBTYPE_ID_A),
228                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
229                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
230                        SUBTYPE_SUBTYPE_ID_A));
231        assertEquals(
232                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
233                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
234                        SUBTYPE_SUBTYPE_ID_A),
235                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
236                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
237                        SUBTYPE_SUBTYPE_ID_A));
238        assertEquals(
239                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
240                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
241                        SUBTYPE_SUBTYPE_ID_A),
242                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
243                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A,
244                        SUBTYPE_SUBTYPE_ID_A));
245        assertEquals(
246                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
247                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
248                        SUBTYPE_SUBTYPE_ID_A),
249                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
250                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B,
251                        SUBTYPE_SUBTYPE_ID_A));
252        assertNotEqual(
253                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
254                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
255                        SUBTYPE_SUBTYPE_ID_A),
256                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
257                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
258                        SUBTYPE_SUBTYPE_ID_B));
259    }
260
261}
262