1/*
2 * Copyright (C) 2017 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.textclassifier;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertThat;
21import static org.mockito.Mockito.mock;
22
23import android.os.LocaleList;
24import android.support.test.InstrumentationRegistry;
25import android.support.test.filters.SmallTest;
26import android.support.test.runner.AndroidJUnit4;
27
28import org.hamcrest.BaseMatcher;
29import org.hamcrest.Description;
30import org.hamcrest.Matcher;
31import org.junit.Before;
32import org.junit.Test;
33import org.junit.runner.RunWith;
34
35@SmallTest
36@RunWith(AndroidJUnit4.class)
37public class TextClassificationManagerTest {
38
39    private static final LocaleList LOCALES = LocaleList.forLanguageTags("en");
40    private static final String NO_TYPE = null;
41
42    private TextClassificationManager mTcm;
43    private TextClassifier mClassifier;
44
45    @Before
46    public void setup() {
47        mTcm = InstrumentationRegistry.getTargetContext()
48                .getSystemService(TextClassificationManager.class);
49        mTcm.setTextClassifier(null);
50        mClassifier = mTcm.getTextClassifier();
51    }
52
53    @Test
54    public void testSmartSelection() {
55        if (isTextClassifierDisabled()) return;
56
57        String text = "Contact me at droid@android.com";
58        String selected = "droid";
59        String suggested = "droid@android.com";
60        int startIndex = text.indexOf(selected);
61        int endIndex = startIndex + selected.length();
62        int smartStartIndex = text.indexOf(suggested);
63        int smartEndIndex = smartStartIndex + suggested.length();
64
65        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
66                isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
67    }
68
69    @Test
70    public void testSmartSelection_nullLocaleList() {
71        if (isTextClassifierDisabled()) return;
72
73        String text = "Contact me at droid@android.com";
74        String selected = "droid";
75        String suggested = "droid@android.com";
76        int startIndex = text.indexOf(selected);
77        int endIndex = startIndex + selected.length();
78        int smartStartIndex = text.indexOf(suggested);
79        int smartEndIndex = smartStartIndex + suggested.length();
80        LocaleList nullLocales = null;
81
82        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, nullLocales),
83                isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
84    }
85
86    @Test
87    public void testSmartSelection_url() {
88        if (isTextClassifierDisabled()) return;
89
90        String text = "Visit http://www.android.com for more information";
91        String selected = "http";
92        String suggested = "http://www.android.com";
93        int startIndex = text.indexOf(selected);
94        int endIndex = startIndex + selected.length();
95        int smartStartIndex = text.indexOf(suggested);
96        int smartEndIndex = smartStartIndex + suggested.length();
97
98        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
99                isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
100    }
101
102    @Test
103    public void testSmartSelection_withEmoji() {
104        if (isTextClassifierDisabled()) return;
105
106        String text = "\uD83D\uDE02 Hello.";
107        String selected = "Hello";
108        int startIndex = text.indexOf(selected);
109        int endIndex = startIndex + selected.length();
110
111        assertThat(mClassifier.suggestSelection(text, startIndex, endIndex, LOCALES),
112                isTextSelection(startIndex, endIndex, NO_TYPE));
113    }
114
115    @Test
116    public void testClassifyText() {
117        if (isTextClassifierDisabled()) return;
118
119        String text = "Contact me at droid@android.com";
120        String classifiedText = "droid@android.com";
121        int startIndex = text.indexOf(classifiedText);
122        int endIndex = startIndex + classifiedText.length();
123        assertThat(mClassifier.classifyText(text, startIndex, endIndex, LOCALES),
124                isTextClassification(
125                        classifiedText,
126                        TextClassifier.TYPE_EMAIL,
127                        "mailto:" + classifiedText));
128    }
129
130    @Test
131    public void testTextClassifyText_url() {
132        if (isTextClassifierDisabled()) return;
133
134        String text = "Visit www.android.com for more information";
135        String classifiedText = "www.android.com";
136        int startIndex = text.indexOf(classifiedText);
137        int endIndex = startIndex + classifiedText.length();
138        assertThat(mClassifier.classifyText(text, startIndex, endIndex, LOCALES),
139                isTextClassification(
140                        classifiedText,
141                        TextClassifier.TYPE_URL,
142                        "http://" + classifiedText));
143    }
144
145    @Test
146    public void testTextClassifyText_url_inCaps() {
147        if (isTextClassifierDisabled()) return;
148
149        String text = "Visit HTTP://ANDROID.COM for more information";
150        String classifiedText = "HTTP://ANDROID.COM";
151        int startIndex = text.indexOf(classifiedText);
152        int endIndex = startIndex + classifiedText.length();
153        assertThat(mClassifier.classifyText(text, startIndex, endIndex, LOCALES),
154                isTextClassification(
155                        classifiedText,
156                        TextClassifier.TYPE_URL,
157                        "http://ANDROID.COM"));
158    }
159
160    @Test
161    public void testTextClassifyText_nullLocaleList() {
162        if (isTextClassifierDisabled()) return;
163
164        String text = "Contact me at droid@android.com";
165        String classifiedText = "droid@android.com";
166        int startIndex = text.indexOf(classifiedText);
167        int endIndex = startIndex + classifiedText.length();
168        LocaleList nullLocales = null;
169        assertThat(mClassifier.classifyText(text, startIndex, endIndex, nullLocales),
170                isTextClassification(
171                        classifiedText,
172                        TextClassifier.TYPE_EMAIL,
173                        "mailto:" + classifiedText));
174    }
175
176    @Test
177    public void testSetTextClassifier() {
178        TextClassifier classifier = mock(TextClassifier.class);
179        mTcm.setTextClassifier(classifier);
180        assertEquals(classifier, mTcm.getTextClassifier());
181    }
182
183    private boolean isTextClassifierDisabled() {
184        return mClassifier == TextClassifier.NO_OP;
185    }
186
187    private static Matcher<TextSelection> isTextSelection(
188            final int startIndex, final int endIndex, final String type) {
189        return new BaseMatcher<TextSelection>() {
190            @Override
191            public boolean matches(Object o) {
192                if (o instanceof TextSelection) {
193                    TextSelection selection = (TextSelection) o;
194                    return startIndex == selection.getSelectionStartIndex()
195                            && endIndex == selection.getSelectionEndIndex()
196                            && typeMatches(selection, type);
197                }
198                return false;
199            }
200
201            private boolean typeMatches(TextSelection selection, String type) {
202                return type == null
203                        || (selection.getEntityCount() > 0
204                                && type.trim().equalsIgnoreCase(selection.getEntity(0)));
205            }
206
207            @Override
208            public void describeTo(Description description) {
209                description.appendValue(
210                        String.format("%d, %d, %s", startIndex, endIndex, type));
211            }
212        };
213    }
214
215    private static Matcher<TextClassification> isTextClassification(
216            final String text, final String type, final String intentUri) {
217        return new BaseMatcher<TextClassification>() {
218            @Override
219            public boolean matches(Object o) {
220                if (o instanceof TextClassification) {
221                    TextClassification result = (TextClassification) o;
222                    final boolean typeRequirementSatisfied;
223                    String scheme;
224                    switch (type) {
225                        case TextClassifier.TYPE_EMAIL:
226                            scheme = result.getIntent().getData().getScheme();
227                            typeRequirementSatisfied = "mailto".equals(scheme);
228                            break;
229                        case TextClassifier.TYPE_URL:
230                            scheme = result.getIntent().getData().getScheme();
231                            typeRequirementSatisfied = "http".equals(scheme)
232                                    || "https".equals(scheme);
233                            break;
234                        default:
235                            typeRequirementSatisfied = true;
236                    }
237
238                    return typeRequirementSatisfied
239                            && text.equals(result.getText())
240                            && result.getEntityCount() > 0
241                            && type.equals(result.getEntity(0))
242                            && intentUri.equals(result.getIntent().getDataString());
243                    // TODO: Include other properties.
244                }
245                return false;
246            }
247
248            @Override
249            public void describeTo(Description description) {
250                description.appendText("text=").appendValue(text)
251                        .appendText(", type=").appendValue(type)
252                        .appendText(", intent.data=").appendValue(intentUri);
253            }
254        };
255    }
256}
257