blob: a3c24cbe3fbd2530c7086d5f90fb92518c17f2f2 [file] [log] [blame]
Abodunrinwa Toki65b25272017-04-13 17:25:59 +01001/*
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
Richard Ledleydb18a572017-11-30 17:33:51 +000019import static org.hamcrest.CoreMatchers.not;
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010020import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertThat;
Jan Althaus108aad32018-01-30 15:26:55 +010022import static org.junit.Assert.assertTrue;
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010023import static org.mockito.Mockito.mock;
24
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000025import android.content.Context;
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010026import android.os.LocaleList;
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000027import android.service.textclassifier.TextClassifierService;
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010028import android.support.test.InstrumentationRegistry;
29import android.support.test.filters.SmallTest;
30import android.support.test.runner.AndroidJUnit4;
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010031
32import org.hamcrest.BaseMatcher;
33import org.hamcrest.Description;
34import org.hamcrest.Matcher;
35import org.junit.Before;
36import org.junit.Test;
37import org.junit.runner.RunWith;
38
Jan Althaus108aad32018-01-30 15:26:55 +010039import java.util.Arrays;
Richard Ledley1fc998b2018-02-16 15:45:06 +000040import java.util.Collections;
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010041import java.util.List;
Jan Althaus108aad32018-01-30 15:26:55 +010042
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010043@SmallTest
44@RunWith(AndroidJUnit4.class)
45public class TextClassificationManagerTest {
46
Lukas Zilkaf8c36bf2018-02-07 20:30:08 +010047 private static final LocaleList LOCALES = LocaleList.forLanguageTags("en-US");
Abodunrinwa Tokib4162972017-05-05 18:07:17 +010048 private static final String NO_TYPE = null;
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010049
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000050 private Context mContext;
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010051 private TextClassificationManager mTcm;
52 private TextClassifier mClassifier;
53
54 @Before
55 public void setup() {
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +000056 mContext = InstrumentationRegistry.getTargetContext();
57 mTcm = mContext.getSystemService(TextClassificationManager.class);
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010058 // Test with the local textClassifier only. (We only bundle "en" model by default).
59 // It's hard to reliably test the results of the device's TextClassifierServiceImpl here.
60 mClassifier = mTcm.getTextClassifier(TextClassifier.LOCAL);
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010061 }
62
63 @Test
64 public void testSmartSelection() {
65 if (isTextClassifierDisabled()) return;
66
67 String text = "Contact me at droid@android.com";
68 String selected = "droid";
69 String suggested = "droid@android.com";
70 int startIndex = text.indexOf(selected);
71 int endIndex = startIndex + selected.length();
72 int smartStartIndex = text.indexOf(suggested);
73 int smartEndIndex = smartStartIndex + suggested.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010074 TextSelection.Request request = new TextSelection.Request.Builder(
75 text, startIndex, endIndex)
76 .setDefaultLocales(LOCALES)
77 .build();
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010078
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010079 TextSelection selection = mClassifier.suggestSelection(request);
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +000080 assertThat(selection,
Abodunrinwa Tokia2df6e52017-04-13 09:56:52 +010081 isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_EMAIL));
82 }
83
84 @Test
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010085 public void testSmartSelection_url() {
86 if (isTextClassifierDisabled()) return;
87
88 String text = "Visit http://www.android.com for more information";
89 String selected = "http";
90 String suggested = "http://www.android.com";
91 int startIndex = text.indexOf(selected);
92 int endIndex = startIndex + selected.length();
93 int smartStartIndex = text.indexOf(suggested);
94 int smartEndIndex = smartStartIndex + suggested.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +010095 TextSelection.Request request = new TextSelection.Request.Builder(
96 text, startIndex, endIndex)
97 .setDefaultLocales(LOCALES)
98 .build();
Abodunrinwa Toki65b25272017-04-13 17:25:59 +010099
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100100 TextSelection selection = mClassifier.suggestSelection(request);
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000101 assertThat(selection,
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100102 isTextSelection(smartStartIndex, smartEndIndex, TextClassifier.TYPE_URL));
103 }
104
105 @Test
Abodunrinwa Tokib4162972017-05-05 18:07:17 +0100106 public void testSmartSelection_withEmoji() {
107 if (isTextClassifierDisabled()) return;
108
109 String text = "\uD83D\uDE02 Hello.";
110 String selected = "Hello";
111 int startIndex = text.indexOf(selected);
112 int endIndex = startIndex + selected.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100113 TextSelection.Request request = new TextSelection.Request.Builder(
114 text, startIndex, endIndex)
115 .setDefaultLocales(LOCALES)
116 .build();
Abodunrinwa Tokib4162972017-05-05 18:07:17 +0100117
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100118 TextSelection selection = mClassifier.suggestSelection(request);
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000119 assertThat(selection,
Abodunrinwa Tokib4162972017-05-05 18:07:17 +0100120 isTextSelection(startIndex, endIndex, NO_TYPE));
121 }
122
123 @Test
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100124 public void testClassifyText() {
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100125 if (isTextClassifierDisabled()) return;
126
127 String text = "Contact me at droid@android.com";
128 String classifiedText = "droid@android.com";
129 int startIndex = text.indexOf(classifiedText);
130 int endIndex = startIndex + classifiedText.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100131 TextClassification.Request request = new TextClassification.Request.Builder(
132 text, startIndex, endIndex)
133 .setDefaultLocales(LOCALES)
134 .build();
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000135
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100136 TextClassification classification = mClassifier.classifyText(request);
Jan Althaus20d346e2018-03-23 14:03:52 +0100137 assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_EMAIL));
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100138 }
139
140 @Test
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100141 public void testTextClassifyText_url() {
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100142 if (isTextClassifierDisabled()) return;
143
Abodunrinwa Toki86ef9822017-05-11 20:05:50 +0100144 String text = "Visit www.android.com for more information";
Abodunrinwa Toki70d41cd2017-05-02 21:43:41 +0100145 String classifiedText = "www.android.com";
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100146 int startIndex = text.indexOf(classifiedText);
147 int endIndex = startIndex + classifiedText.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100148 TextClassification.Request request = new TextClassification.Request.Builder(
149 text, startIndex, endIndex)
150 .setDefaultLocales(LOCALES)
151 .build();
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000152
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100153 TextClassification classification = mClassifier.classifyText(request);
Jan Althaus20d346e2018-03-23 14:03:52 +0100154 assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
Abodunrinwa Toki86ef9822017-05-11 20:05:50 +0100155 }
156
157 @Test
Jan Althauseaff57e2018-02-12 12:47:27 +0100158 public void testTextClassifyText_address() {
159 if (isTextClassifierDisabled()) return;
160
161 String text = "Brandschenkestrasse 110, Zürich, Switzerland";
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100162 TextClassification.Request request = new TextClassification.Request.Builder(
163 text, 0, text.length())
164 .setDefaultLocales(LOCALES)
165 .build();
166
167 TextClassification classification = mClassifier.classifyText(request);
Jan Althaus20d346e2018-03-23 14:03:52 +0100168 assertThat(classification, isTextClassification(text, TextClassifier.TYPE_ADDRESS));
Jan Althauseaff57e2018-02-12 12:47:27 +0100169 }
170
171 @Test
Abodunrinwa Toki86ef9822017-05-11 20:05:50 +0100172 public void testTextClassifyText_url_inCaps() {
173 if (isTextClassifierDisabled()) return;
174
175 String text = "Visit HTTP://ANDROID.COM for more information";
176 String classifiedText = "HTTP://ANDROID.COM";
177 int startIndex = text.indexOf(classifiedText);
178 int endIndex = startIndex + classifiedText.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100179 TextClassification.Request request = new TextClassification.Request.Builder(
180 text, startIndex, endIndex)
181 .setDefaultLocales(LOCALES)
182 .build();
Abodunrinwa Toki4d232d62017-11-23 12:22:45 +0000183
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100184 TextClassification classification = mClassifier.classifyText(request);
Jan Althaus20d346e2018-03-23 14:03:52 +0100185 assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_URL));
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100186 }
187
188 @Test
Lukas Zilkaf8c36bf2018-02-07 20:30:08 +0100189 public void testTextClassifyText_date() {
190 if (isTextClassifierDisabled()) return;
191
192 String text = "Let's meet on January 9, 2018.";
193 String classifiedText = "January 9, 2018";
194 int startIndex = text.indexOf(classifiedText);
195 int endIndex = startIndex + classifiedText.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100196 TextClassification.Request request = new TextClassification.Request.Builder(
197 text, startIndex, endIndex)
198 .setDefaultLocales(LOCALES)
199 .build();
Lukas Zilkaf8c36bf2018-02-07 20:30:08 +0100200
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100201 TextClassification classification = mClassifier.classifyText(request);
Jan Althaus20d346e2018-03-23 14:03:52 +0100202 assertThat(classification, isTextClassification(classifiedText, TextClassifier.TYPE_DATE));
Lukas Zilkaf8c36bf2018-02-07 20:30:08 +0100203 }
204
205 @Test
206 public void testTextClassifyText_datetime() {
207 if (isTextClassifierDisabled()) return;
208
209 String text = "Let's meet 2018/01/01 10:30:20.";
210 String classifiedText = "2018/01/01 10:30:20";
211 int startIndex = text.indexOf(classifiedText);
212 int endIndex = startIndex + classifiedText.length();
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100213 TextClassification.Request request = new TextClassification.Request.Builder(
214 text, startIndex, endIndex)
215 .setDefaultLocales(LOCALES)
216 .build();
Lukas Zilkaf8c36bf2018-02-07 20:30:08 +0100217
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100218 TextClassification classification = mClassifier.classifyText(request);
Lukas Zilkaf8c36bf2018-02-07 20:30:08 +0100219 assertThat(classification,
Jan Althaus20d346e2018-03-23 14:03:52 +0100220 isTextClassification(classifiedText, TextClassifier.TYPE_DATE_TIME));
Lukas Zilkaf8c36bf2018-02-07 20:30:08 +0100221 }
222
223 @Test
Richard Ledleydb18a572017-11-30 17:33:51 +0000224 public void testGenerateLinks_phone() {
Richard Ledley68d94522017-10-05 10:52:19 +0100225 if (isTextClassifierDisabled()) return;
Richard Ledleydb18a572017-11-30 17:33:51 +0000226 String text = "The number is +12122537077. See you tonight!";
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100227 TextLinks.Request request = new TextLinks.Request.Builder(text).build();
228 assertThat(mClassifier.generateLinks(request),
Richard Ledleydb18a572017-11-30 17:33:51 +0000229 isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE));
230 }
Richard Ledley68d94522017-10-05 10:52:19 +0100231
Richard Ledleydb18a572017-11-30 17:33:51 +0000232 @Test
233 public void testGenerateLinks_exclude() {
234 if (isTextClassifierDisabled()) return;
235 String text = "The number is +12122537077. See you tonight!";
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100236 List<String> hints = Collections.EMPTY_LIST;
237 List<String> included = Collections.EMPTY_LIST;
238 List<String> excluded = Arrays.asList(TextClassifier.TYPE_PHONE);
239 TextLinks.Request request = new TextLinks.Request.Builder(text)
240 .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
241 .setDefaultLocales(LOCALES)
242 .build();
243 assertThat(mClassifier.generateLinks(request),
Richard Ledleydb18a572017-11-30 17:33:51 +0000244 not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
245 }
Richard Ledley68d94522017-10-05 10:52:19 +0100246
Richard Ledleydb18a572017-11-30 17:33:51 +0000247 @Test
Richard Ledley1fc998b2018-02-16 15:45:06 +0000248 public void testGenerateLinks_explicit_address() {
Richard Ledleydb18a572017-11-30 17:33:51 +0000249 if (isTextClassifierDisabled()) return;
Richard Ledley1fc998b2018-02-16 15:45:06 +0000250 String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!";
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100251 List<String> explicit = Arrays.asList(TextClassifier.TYPE_ADDRESS);
252 TextLinks.Request request = new TextLinks.Request.Builder(text)
253 .setEntityConfig(TextClassifier.EntityConfig.createWithExplicitEntityList(explicit))
254 .setDefaultLocales(LOCALES)
255 .build();
256 assertThat(mClassifier.generateLinks(request),
Richard Ledley1fc998b2018-02-16 15:45:06 +0000257 isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA",
258 TextClassifier.TYPE_ADDRESS));
259 }
Jan Althaus108aad32018-01-30 15:26:55 +0100260
Richard Ledley1fc998b2018-02-16 15:45:06 +0000261 @Test
262 public void testGenerateLinks_exclude_override() {
263 if (isTextClassifierDisabled()) return;
Richard Ledleydb18a572017-11-30 17:33:51 +0000264 String text = "The number is +12122537077. See you tonight!";
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100265 List<String> hints = Collections.EMPTY_LIST;
266 List<String> included = Arrays.asList(TextClassifier.TYPE_PHONE);
267 List<String> excluded = Arrays.asList(TextClassifier.TYPE_PHONE);
268 TextLinks.Request request = new TextLinks.Request.Builder(text)
269 .setEntityConfig(TextClassifier.EntityConfig.create(hints, included, excluded))
270 .setDefaultLocales(LOCALES)
271 .build();
272 assertThat(mClassifier.generateLinks(request),
Richard Ledleydb18a572017-11-30 17:33:51 +0000273 not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)));
274 }
Richard Ledley68d94522017-10-05 10:52:19 +0100275
Richard Ledleydb18a572017-11-30 17:33:51 +0000276 @Test
Jan Althaus108aad32018-01-30 15:26:55 +0100277 public void testGenerateLinks_maxLength() {
278 if (isTextClassifierDisabled()) return;
279 char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength()];
280 Arrays.fill(manySpaces, ' ');
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100281 TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
282 TextLinks links = mClassifier.generateLinks(request);
Jan Althaus108aad32018-01-30 15:26:55 +0100283 assertTrue(links.getLinks().isEmpty());
284 }
285
286 @Test(expected = IllegalArgumentException.class)
287 public void testGenerateLinks_tooLong() {
288 if (isTextClassifierDisabled()) {
289 throw new IllegalArgumentException("pass if disabled");
290 }
291 char[] manySpaces = new char[mClassifier.getMaxGenerateLinksTextLength() + 1];
292 Arrays.fill(manySpaces, ' ');
Abodunrinwa Toki080c8542018-03-27 00:04:06 +0100293 TextLinks.Request request = new TextLinks.Request.Builder(new String(manySpaces)).build();
294 mClassifier.generateLinks(request);
Jan Althaus108aad32018-01-30 15:26:55 +0100295 }
296
297 @Test
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100298 public void testSetTextClassifier() {
299 TextClassifier classifier = mock(TextClassifier.class);
300 mTcm.setTextClassifier(classifier);
301 assertEquals(classifier, mTcm.getTextClassifier());
302 }
303
Abodunrinwa Tokic7073a42018-02-28 23:02:13 +0000304 @Test
305 public void testGetLocalTextClassifier() {
306 assertTrue(mTcm.getTextClassifier(TextClassifier.LOCAL) instanceof TextClassifierImpl);
307 }
308
309 @Test
310 public void testGetSystemTextClassifier() {
311 assertTrue(
312 TextClassifierService.getServiceComponentName(mContext) == null
313 || mTcm.getTextClassifier(TextClassifier.SYSTEM) instanceof SystemTextClassifier);
314 }
315
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100316 private boolean isTextClassifierDisabled() {
317 return mClassifier == TextClassifier.NO_OP;
318 }
319
320 private static Matcher<TextSelection> isTextSelection(
321 final int startIndex, final int endIndex, final String type) {
322 return new BaseMatcher<TextSelection>() {
323 @Override
324 public boolean matches(Object o) {
325 if (o instanceof TextSelection) {
326 TextSelection selection = (TextSelection) o;
327 return startIndex == selection.getSelectionStartIndex()
328 && endIndex == selection.getSelectionEndIndex()
Abodunrinwa Tokib4162972017-05-05 18:07:17 +0100329 && typeMatches(selection, type);
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100330 }
331 return false;
332 }
333
Abodunrinwa Tokib4162972017-05-05 18:07:17 +0100334 private boolean typeMatches(TextSelection selection, String type) {
335 return type == null
336 || (selection.getEntityCount() > 0
337 && type.trim().equalsIgnoreCase(selection.getEntity(0)));
338 }
339
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100340 @Override
341 public void describeTo(Description description) {
342 description.appendValue(
343 String.format("%d, %d, %s", startIndex, endIndex, type));
344 }
345 };
346 }
347
Richard Ledleydb18a572017-11-30 17:33:51 +0000348 private static Matcher<TextLinks> isTextLinksContaining(
349 final String text, final String substring, final String type) {
350 return new BaseMatcher<TextLinks>() {
351
352 @Override
353 public void describeTo(Description description) {
354 description.appendText("text=").appendValue(text)
355 .appendText(", substring=").appendValue(substring)
356 .appendText(", type=").appendValue(type);
357 }
358
359 @Override
360 public boolean matches(Object o) {
361 if (o instanceof TextLinks) {
362 for (TextLinks.TextLink link : ((TextLinks) o).getLinks()) {
363 if (text.subSequence(link.getStart(), link.getEnd()).equals(substring)) {
364 return type.equals(link.getEntity(0));
365 }
366 }
367 }
368 return false;
369 }
370 };
371 }
372
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100373 private static Matcher<TextClassification> isTextClassification(
Jan Althaus20d346e2018-03-23 14:03:52 +0100374 final String text, final String type) {
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100375 return new BaseMatcher<TextClassification>() {
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100376 @Override
377 public boolean matches(Object o) {
Abodunrinwa Tokie0b57892017-04-28 19:59:57 +0100378 if (o instanceof TextClassification) {
379 TextClassification result = (TextClassification) o;
Jan Althaus20d346e2018-03-23 14:03:52 +0100380 return text.equals(result.getText())
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100381 && result.getEntityCount() > 0
Jan Althaus20d346e2018-03-23 14:03:52 +0100382 && type.equals(result.getEntity(0));
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100383 }
384 return false;
385 }
386
387 @Override
388 public void describeTo(Description description) {
389 description.appendText("text=").appendValue(text)
Jan Althaus20d346e2018-03-23 14:03:52 +0100390 .appendText(", type=").appendValue(type);
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100391 }
392 };
393 }
Abodunrinwa Toki65b25272017-04-13 17:25:59 +0100394}