1 /*
2  * Copyright (C) 2018 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 
17 package com.android.textclassifier;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import android.app.UiAutomation;
22 import android.content.pm.PackageManager;
23 import android.content.pm.PackageManager.NameNotFoundException;
24 import android.icu.util.ULocale;
25 import android.provider.DeviceConfig;
26 import android.view.textclassifier.ConversationAction;
27 import android.view.textclassifier.ConversationActions;
28 import android.view.textclassifier.TextClassification;
29 import android.view.textclassifier.TextClassificationManager;
30 import android.view.textclassifier.TextClassifier;
31 import android.view.textclassifier.TextLanguage;
32 import android.view.textclassifier.TextLinks;
33 import android.view.textclassifier.TextLinks.TextLink;
34 import android.view.textclassifier.TextSelection;
35 import androidx.test.core.app.ApplicationProvider;
36 import androidx.test.ext.junit.runners.AndroidJUnit4;
37 import androidx.test.filters.SmallTest;
38 import androidx.test.platform.app.InstrumentationRegistry;
39 import com.google.common.collect.ImmutableList;
40 import java.util.ArrayList;
41 import java.util.List;
42 import org.junit.Before;
43 import org.junit.Rule;
44 import org.junit.Test;
45 import org.junit.rules.ExternalResource;
46 import org.junit.runner.RunWith;
47 
48 /**
49  * End-to-end tests for the {@link TextClassifier} APIs. Unlike {@link TextClassifierImplTest}.
50  *
51  * <p>Unlike {@link TextClassifierImplTest}, we are trying to run the tests in a environment that is
52  * closer to the production environment. For example, we are not injecting the model files.
53  */
54 @SmallTest
55 @RunWith(AndroidJUnit4.class)
56 public class TextClassifierApiTest {
57 
58   private TextClassifier textClassifier;
59 
60   @Rule
61   public final ExtServicesTextClassifierRule extServicesTextClassifierRule =
62       new ExtServicesTextClassifierRule();
63 
64   @Before
setup()65   public void setup() {
66     textClassifier = extServicesTextClassifierRule.getTextClassifier();
67   }
68 
69   @Test
suggestSelection()70   public void suggestSelection() {
71     String text = "Visit http://www.android.com for more information";
72     String selected = "http";
73     String suggested = "http://www.android.com";
74     int startIndex = text.indexOf(selected);
75     int endIndex = startIndex + selected.length();
76     int smartStartIndex = text.indexOf(suggested);
77     int smartEndIndex = smartStartIndex + suggested.length();
78 
79     TextSelection.Request request =
80         new TextSelection.Request.Builder(text, startIndex, endIndex).build();
81 
82     TextSelection selection = textClassifier.suggestSelection(request);
83     assertThat(selection.getEntityCount()).isGreaterThan(0);
84     assertThat(selection.getEntity(0)).isEqualTo(TextClassifier.TYPE_URL);
85     assertThat(selection.getSelectionStartIndex()).isEqualTo(smartStartIndex);
86     assertThat(selection.getSelectionEndIndex()).isEqualTo(smartEndIndex);
87   }
88 
89   @Test
classifyText()90   public void classifyText() {
91     String text = "Contact me at droid@android.com";
92     String classifiedText = "droid@android.com";
93     int startIndex = text.indexOf(classifiedText);
94     int endIndex = startIndex + classifiedText.length();
95     TextClassification.Request request =
96         new TextClassification.Request.Builder(text, startIndex, endIndex).build();
97 
98     TextClassification classification = textClassifier.classifyText(request);
99     assertThat(classification.getEntityCount()).isGreaterThan(0);
100     assertThat(classification.getEntity(0)).isEqualTo(TextClassifier.TYPE_EMAIL);
101     assertThat(classification.getText()).isEqualTo(classifiedText);
102     assertThat(classification.getActions()).isNotEmpty();
103   }
104 
105   @Test
generateLinks()106   public void generateLinks() {
107     String text = "Check this out, http://www.android.com";
108 
109     TextLinks.Request request = new TextLinks.Request.Builder(text).build();
110 
111     TextLinks textLinks = textClassifier.generateLinks(request);
112 
113     List<TextLink> links = new ArrayList<>(textLinks.getLinks());
114     assertThat(textLinks.getText().toString()).isEqualTo(text);
115     assertThat(links).hasSize(1);
116     assertThat(links.get(0).getEntityCount()).isGreaterThan(0);
117     assertThat(links.get(0).getEntity(0)).isEqualTo(TextClassifier.TYPE_URL);
118     assertThat(links.get(0).getConfidenceScore(TextClassifier.TYPE_URL)).isGreaterThan(0f);
119   }
120 
121   @Test
detectedLanguage()122   public void detectedLanguage() {
123     String text = "朝、ピカチュウ";
124     TextLanguage.Request request = new TextLanguage.Request.Builder(text).build();
125 
126     TextLanguage textLanguage = textClassifier.detectLanguage(request);
127 
128     assertThat(textLanguage.getLocaleHypothesisCount()).isGreaterThan(0);
129     assertThat(textLanguage.getLocale(0).getLanguage()).isEqualTo("ja");
130     assertThat(textLanguage.getConfidenceScore(ULocale.JAPANESE)).isGreaterThan(0f);
131   }
132 
133   @Test
suggestConversationActions()134   public void suggestConversationActions() {
135     ConversationActions.Message message =
136         new ConversationActions.Message.Builder(ConversationActions.Message.PERSON_USER_OTHERS)
137             .setText("Check this out: https://www.android.com")
138             .build();
139     ConversationActions.Request request =
140         new ConversationActions.Request.Builder(ImmutableList.of(message)).build();
141 
142     ConversationActions conversationActions = textClassifier.suggestConversationActions(request);
143 
144     assertThat(conversationActions.getConversationActions()).hasSize(1);
145     ConversationAction conversationAction = conversationActions.getConversationActions().get(0);
146     assertThat(conversationAction.getType()).isEqualTo(ConversationAction.TYPE_OPEN_URL);
147     assertThat(conversationAction.getAction()).isNotNull();
148   }
149 
150   /** A rule that manages a text classifier that is backed by the ExtServices. */
151   private static class ExtServicesTextClassifierRule extends ExternalResource {
152     private static final String CONFIG_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE =
153         "textclassifier_service_package_override";
154     private static final String PKG_NAME_GOOGLE_EXTSERVICES = "com.google.android.ext.services";
155     private static final String PKG_NAME_AOSP_EXTSERVICES = "android.ext.services";
156 
157     private String textClassifierServiceOverrideFlagOldValue;
158 
159     @Override
before()160     protected void before() {
161       UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
162       try {
163         uiAutomation.adoptShellPermissionIdentity();
164         textClassifierServiceOverrideFlagOldValue =
165             DeviceConfig.getString(
166                 DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
167                 CONFIG_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
168                 null);
169         DeviceConfig.setProperty(
170             DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
171             CONFIG_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
172             getExtServicesPackageName(),
173             /* makeDefault= */ false);
174       } finally {
175         uiAutomation.dropShellPermissionIdentity();
176       }
177     }
178 
179     @Override
after()180     protected void after() {
181       UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
182       try {
183         uiAutomation.adoptShellPermissionIdentity();
184         DeviceConfig.setProperty(
185             DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
186             CONFIG_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE,
187             textClassifierServiceOverrideFlagOldValue,
188             /* makeDefault= */ false);
189       } finally {
190         uiAutomation.dropShellPermissionIdentity();
191       }
192     }
193 
getExtServicesPackageName()194     private static String getExtServicesPackageName() {
195       PackageManager packageManager =
196           ApplicationProvider.getApplicationContext().getPackageManager();
197       try {
198         packageManager.getApplicationInfo(PKG_NAME_GOOGLE_EXTSERVICES, /* flags= */ 0);
199         return PKG_NAME_GOOGLE_EXTSERVICES;
200       } catch (NameNotFoundException e) {
201         return PKG_NAME_AOSP_EXTSERVICES;
202       }
203     }
204 
getTextClassifier()205     public TextClassifier getTextClassifier() {
206       TextClassificationManager textClassificationManager =
207           ApplicationProvider.getApplicationContext()
208               .getSystemService(TextClassificationManager.class);
209       return textClassificationManager.getTextClassifier();
210     }
211   }
212 }
213