1/*
2 * Copyright (C) 2012 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 com.android.dialer.dialpad;
18
19import android.test.suitebuilder.annotation.SmallTest;
20import android.test.suitebuilder.annotation.Suppress;
21import android.util.Log;
22import android.test.AndroidTestCase;
23
24import com.android.dialer.dialpad.SmartDialNameMatcher;
25import com.android.dialer.dialpad.SmartDialPrefix;
26
27import java.text.Normalizer;
28import java.util.ArrayList;
29
30import junit.framework.TestCase;
31
32@SmallTest
33public class SmartDialNameMatcherTest extends TestCase {
34    private static final String TAG = "SmartDialNameMatcherTest";
35
36    public void testMatches() {
37        // Test to ensure that all alphabetic characters are covered
38        checkMatches("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
39                "22233344455566677778889999" + "22233344455566677778889999", true, 0, 26 * 2);
40        // Should fail because of a mistyped 2 instead of 9 in the second last character
41        checkMatches("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
42                "22233344455566677778889999" + "22233344455566677778889929", false, 0, 0);
43
44        // Basic name test
45        checkMatches("joe", "5", true, 0, 1);
46        checkMatches("joe", "56", true, 0, 2);
47        checkMatches("joe", "563", true, 0, 3);
48
49        // Matches only word boundary.
50        checkMatches("joe", "63", false, 0, 0);
51        checkMatches("joe oe", "63", true, 4, 6);
52
53        // Test for a match across word boundaries
54        checkMatches("joe oe", "56363", true, 0, 6);
55    }
56
57    public void testMatches_repeatedLetters() {
58        checkMatches("aaaaaaaaaa", "2222222222", true, 0, 10);
59        // Fails because of one extra 2
60        checkMatches("aaaaaaaaaa", "22222222222", false, 0, 0);
61        checkMatches("zzzzzzzzzz zzzzzzzzzz", "99999999999999999999", true, 0, 21);
62    }
63
64    public void testMatches_repeatedSpaces() {
65        checkMatches("William     J  Smith", "9455426576", true, 0, 17);
66        checkMatches("William     J  Smith", "576", true, 12, 17);
67        // Fails because we start at non-word boundary
68        checkMatches("William     J  Smith", "6576", false, 0, 0);
69    }
70
71
72    public void testMatches_Initial() {
73        // wjs matches (W)illiam (J)ohn (S)mith
74        checkMatches("William John Smith", "957", true, 0, 1, 8, 9, 13, 14);
75        // wjsmit matches (W)illiam (J)ohn (Smit)h
76        checkMatches("William John Smith", "957648", true, 0, 1, 8, 9, 13, 17);
77        // wjohn matches (W)illiam (John) Smith
78        checkMatches("William John Smith", "95646", true, 0, 1, 8, 12);
79        // jsmi matches William (J)ohn (Smi)th
80        checkMatches("William John Smith", "5764", true, 8, 9, 13, 16);
81        // make sure multiple spaces don't mess things up
82        checkMatches("William        John   Smith", "5764", true, 15, 16, 22, 25);
83    }
84
85    public void testMatches_InitialWithSeparator() {
86        // wjs matches (W)illiam (J)ohn (S)mith
87        checkMatches("William John-Smith", "957", true, 0, 1, 8, 9, 13, 14);
88        // wjsmit matches (W)illiam (J)ohn-(OShe)a
89        checkMatches("William John-O'Shea", "956743", true, 0, 1, 8, 9, 13, 18);
90        // wjohn matches (W)illiam-(John) Smith
91        checkMatches("William-John Smith", "95646", true, 0, 1, 8, 12);
92        // jsmi matches William (J)ohn-(Smi)th
93        checkMatches("William John-Smith", "5764", true, 8, 9, 13, 16);
94        // wsmi matches (W)illiam John (Smi)th
95        checkMatches("William John-Smith", "9764", true, 0, 1, 13, 16);
96        // make sure multiple spaces don't mess things up
97        checkMatches("William        John---Smith", "5764", true, 15, 16, 22, 25);
98        // match tokens that are located directly after a non-space separator (studio)
99        checkMatches("Berkeley Hair-Studio", "788346", true, 14, 20);
100        // match tokens with same initials
101        checkMatches("H.Harold", "427653", true, 2, 8);
102        // various matching combinations of tokens with similar initials
103        checkMatches("Yo-Yoghurt Land", "964487", true, 3, 9);
104        checkMatches("Yo-Yoghurt Land", "96448785263", true, 3, 15);
105        checkMatches("Yo-Yoghurt Land", "95263", true, 3, 4, 11, 15);
106        checkMatches("Yo-Yoghurt Land", "995263", true, 0, 1, 3, 4, 11, 15);
107
108        checkMatches("ab zz ef", "23", true, 0, 1, 6, 7);
109    }
110
111    public void testMatches_repeatedSeparators() {
112        // Simple match for single token
113        checkMatches("John,,,,,Doe", "5646", true, 0, 4);
114        // Match across tokens
115        checkMatches("John,,,,,Doe", "56463", true, 0, 10);
116        // Match token after chain of separators
117        checkMatches("John,,,,,Doe", "363", true, 9, 12);
118    }
119
120    public void testMatches_LatinMix() {
121        // Latin + Chinese characters
122        checkMatches("Lee王力Wang宏", "59264", true, 0, 1, 5, 9);
123        // Latin + Japanese characters
124        checkMatches("千Abcd佳智Efgh佳IJKL", "222333444555", true, 1, 16);
125        // Latin + Arabic characters
126        checkMatches("Peterعبد الرحمنJames", "752637", true, 0, 1, 15, 20);
127    }
128
129    public void testMatches_umlaut() {
130        checkMatches("ÄÖÜäöü", "268268", true, 0, 6);
131    }
132
133    public void testMatches_NumberInName() {
134        // Number used as display name
135        checkMatches("+1-123-456-6789", "1234566789", true, 3, 15);
136        // Mix of numbers and letters
137        checkMatches("3rd Grade Teacher", "373", true, 0, 3);
138        checkMatches("1800 Win A Prize", "1800", true, 0, 4);
139        checkMatches("1800 Win A Prize", "1800946277493", true, 0, 16);
140        checkMatches("1800 Win A Prize", "977493", true, 5, 6, 11, 16);
141    }
142
143
144    // TODO: Great if it was treated as "s" or "ss. Figure out if possible without prefix trie?
145    @Suppress
146    public void testMatches_germanSharpS() {
147        checkMatches("ß", "s", true, 0, 1);
148        checkMatches("ß", "ss", true, 0, 1);
149    }
150
151    // TODO: Add this and make it work
152    @Suppress
153    public void testMatches_greek() {
154        // http://en.wikipedia.org/wiki/Greek_alphabet
155        fail("Greek letters aren't supported yet.");
156    }
157
158    // TODO: Add this and make it work
159    @Suppress
160    public void testMatches_cyrillic() {
161        // http://en.wikipedia.org/wiki/Cyrillic_script
162        fail("Cyrillic letters aren't supported yet.");
163    }
164
165
166    public void testMatches_NumberBasic() {
167        // Simple basic examples that start the match from the start of the number
168        checkMatchesNumber("5103337596", "510", true, 0, 3);
169        checkMatchesNumber("5103337596", "511", false, 0, 0);
170        checkMatchesNumber("5103337596", "5103337596", true, 0, 10);
171        checkMatchesNumber("123-456-789", "123456789", true, 0, 11);
172        checkMatchesNumber("123-456-789", "123456788", false, 0, 0);
173        checkMatchesNumber("09999999999", "099", true, 0, 3);
174    }
175
176    public void testMatches_NumberWithCountryCode() {
177        // These matches should ignore the country prefix
178        // USA (+1)
179        checkMatchesNumber("+15103337596", "5103337596", true, 2, 12);
180        checkMatchesNumber("+15103337596", "15103337596", true, 0, 12);
181
182        // Singapore (+65)
183        checkMatchesNumber("+6591776930", "6591", true, 0, 5);
184        checkMatchesNumber("+6591776930", "9177", true, 3, 7);
185        checkMatchesNumber("+6591776930", "5917", false, 3, 7);
186
187        // Hungary (+36)
188        checkMatchesNumber("+3612345678", "361234", true, 0, 7);
189        checkMatchesNumber("+3612345678", "1234", true, 3, 7);
190
191        // Hongkong (+852)
192        checkMatchesNumber("+852 2222 2222", "85222222222", true, 0, 14);
193        checkMatchesNumber("+852 2222 3333", "2222", true, 5, 9);
194
195        // Invalid (+854)
196        checkMatchesNumber("+854 1111 2222", "8541111", true, 0, 9);
197        checkMatchesNumber("+854 1111 2222", "1111", false, 0, 0);
198    }
199
200    public void testMatches_NumberNANP() {
201        SmartDialPrefix.setUserInNanpRegion(true);
202        // An 11 digit number prefixed with 1 should be matched by the 10 digit number, as well as
203        // the 7 digit number (without area code)
204        checkMatchesNumber("1-510-333-7596", "5103337596", true, true, 2, 14);
205        checkMatchesNumber("1-510-333-7596", "3337596", true, true, 6, 14);
206
207        // An 11 digit number prefixed with +1 should be matched by the 10 digit number, as well as
208        // the 7 digit number (without area code)
209        checkMatchesNumber("+1-510-333-7596", "5103337596", true, true, 3, 15);
210        checkMatchesNumber("+1-510-333-7596", "3337596", true, true, 7, 15);
211        checkMatchesNumber("+1-510-333-7596", "103337596", false, true, 0, 0);
212        checkMatchesNumber("+1-510-333-7596", "337596", false, true, 0, 0);
213        checkMatchesNumber("+1510 3337596", "5103337596", true, true, 2, 13);
214        checkMatchesNumber("+1510 3337596", "3337596", true, true, 6, 13);
215        checkMatchesNumber("+1510 3337596", "103337596", false, true, 0, 0);
216        checkMatchesNumber("+1510 3337596", "37596", false, true, 0, 0);
217
218        // Invalid NANP numbers should not be matched
219        checkMatchesNumber("1-510-333-759", "510333759", false, true, 0, 0);
220        checkMatchesNumber("510-333-759", "333759", false, true, 0, 0);
221
222        // match should fail if NANP flag is switched off
223        checkMatchesNumber("1-510-333-7596", "3337596", false, false, 0, 0);
224
225        // A 10 digit number without a 1 prefix should be matched by the 7 digit number
226        checkMatchesNumber("(650) 292 2323", "2922323", true, true, 6, 14);
227        checkMatchesNumber("(650) 292 2323", "6502922323", true, true, 0, 14);
228        // match should fail if NANP flag is switched off
229        checkMatchesNumber("(650) 292 2323", "2922323", false, false, 0, 0);
230        // But this should still match (since it is the full number)
231        checkMatchesNumber("(650) 292 2323", "6502922323", true, false, 0, 14);
232    }
233
234
235    private void checkMatchesNumber(String number, String query, boolean expectedMatches,
236            int matchStart, int matchEnd) {
237        checkMatchesNumber(number, query, expectedMatches, false, matchStart, matchEnd);
238    }
239
240    private void checkMatchesNumber(String number, String query, boolean expectedMatches,
241            boolean matchNanp, int matchStart, int matchEnd) {
242        final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query);
243        final SmartDialMatchPosition pos = matcher.matchesNumber(number, query, matchNanp);
244        assertEquals(expectedMatches, pos != null);
245        if (expectedMatches) {
246            assertEquals("start", matchStart, pos.start);
247            assertEquals("end", matchEnd, pos.end);
248        }
249    }
250
251    private void checkMatches(String displayName, String query, boolean expectedMatches,
252            int... expectedMatchPositions) {
253        final SmartDialNameMatcher matcher = new SmartDialNameMatcher(query);
254        final ArrayList<SmartDialMatchPosition> matchPositions =
255                new ArrayList<SmartDialMatchPosition>();
256        final boolean matches = matcher.matchesCombination(
257                displayName, query, matchPositions);
258        Log.d(TAG, "query=" + query + "  text=" + displayName
259                + "  nfd=" + Normalizer.normalize(displayName, Normalizer.Form.NFD)
260                + "  nfc=" + Normalizer.normalize(displayName, Normalizer.Form.NFC)
261                + "  nfkd=" + Normalizer.normalize(displayName, Normalizer.Form.NFKD)
262                + "  nfkc=" + Normalizer.normalize(displayName, Normalizer.Form.NFKC)
263                + "  matches=" + matches);
264        assertEquals("matches", expectedMatches, matches);
265        final int length = expectedMatchPositions.length;
266        assertEquals(length % 2, 0);
267        if (matches) {
268            for (int i = 0; i < length/2; i++) {
269                assertEquals("start", expectedMatchPositions[i * 2], matchPositions.get(i).start);
270                assertEquals("end", expectedMatchPositions[i * 2 + 1], matchPositions.get(i).end);
271            }
272        }
273    }
274
275}
276