• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package android.view.textclassifier.cts;
17 
18 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
19 
20 import static com.google.common.truth.Truth.assertThat;
21 
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.app.PendingIntent;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.graphics.drawable.Icon;
29 import android.os.CancellationSignal;
30 import android.os.SystemClock;
31 import android.service.textclassifier.TextClassifierService;
32 import android.view.textclassifier.ConversationActions;
33 import android.view.textclassifier.SelectionEvent;
34 import android.view.textclassifier.TextClassification;
35 import android.view.textclassifier.TextClassificationContext;
36 import android.view.textclassifier.TextClassificationManager;
37 import android.view.textclassifier.TextClassificationSessionId;
38 import android.view.textclassifier.TextClassifier;
39 import android.view.textclassifier.TextSelection;
40 
41 import androidx.core.os.BuildCompat;
42 import androidx.test.InstrumentationRegistry;
43 import androidx.test.core.app.ApplicationProvider;
44 import androidx.test.runner.AndroidJUnit4;
45 
46 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
47 import com.android.compatibility.common.util.RequiredServiceRule;
48 import com.android.compatibility.common.util.SafeCleanerRule;
49 
50 import org.junit.Rule;
51 import org.junit.Test;
52 import org.junit.rules.RuleChain;
53 import org.junit.runner.RunWith;
54 
55 import java.util.Collections;
56 import java.util.List;
57 import java.util.concurrent.CountDownLatch;
58 
59 /**
60  * Tests for TextClassifierService query related functions.
61  *
62  * <p>
63  * We use a non-standard TextClassifierService for TextClassifierService-related CTS tests. A
64  * non-standard TextClassifierService that is set via device config. This non-standard
65  * TextClassifierService is not defined in the trust TextClassifierService, it should only receive
66  * queries from clients in the same package.
67  */
68 @RunWith(AndroidJUnit4.class)
69 public class TextClassifierServiceSwapTest {
70     // TODO: Add more tests to verify all the TC APIs call between caller and TCS.
71     private static final String TAG = "TextClassifierServiceSwapTest";
72 
73     private final TextClassifierTestWatcher mTestWatcher =
74             CtsTextClassifierService.getTestWatcher();
75     private final SafeCleanerRule mSafeCleanerRule = mTestWatcher.newSafeCleaner();
76     private final TextClassificationContext mTextClassificationContext =
77             new TextClassificationContext.Builder(
78                     ApplicationProvider.getApplicationContext().getPackageName(),
79                     TextClassifier.WIDGET_TYPE_EDIT_WEBVIEW)
80                     .build();
81     private final RequiredServiceRule mRequiredServiceRule =
82             new RequiredServiceRule(Context.TEXT_CLASSIFICATION_SERVICE);
83 
84     @Rule
85     public final RuleChain mAllRules = RuleChain
86             .outerRule(mRequiredServiceRule)
87             .around(mTestWatcher)
88             .around(mSafeCleanerRule);
89 
90     @Test
testOutsideOfPackageActivity_noRequestReceived()91     public void testOutsideOfPackageActivity_noRequestReceived() throws Exception {
92         // Start an Activity from another package to trigger a TextClassifier call
93         runQueryTextClassifierServiceActivity();
94 
95         // Wait for the TextClassifierService to connect.
96         // Note that the system requires a query to the TextClassifierService before it is
97         // first connected.
98         final CtsTextClassifierService service = mTestWatcher.getService();
99 
100         // Wait a delay for the query is delivered.
101         service.awaitQuery();
102 
103         // Verify the request was not passed to the service.
104         assertThat(service.getRequestSessions()).isEmpty();
105     }
106 
107     @Test
testMultipleActiveSessions()108     public void testMultipleActiveSessions() throws Exception {
109         final TextClassification.Request request =
110                 new TextClassification.Request.Builder("Hello World", 0, 1).build();
111         final TextClassificationManager tcm =
112                 ApplicationProvider.getApplicationContext().getSystemService(
113                         TextClassificationManager.class);
114         final TextClassifier firstSession =
115                 tcm.createTextClassificationSession(mTextClassificationContext);
116         final TextClassifier secondSessionSession =
117                 tcm.createTextClassificationSession(mTextClassificationContext);
118 
119         firstSession.classifyText(request);
120         final CtsTextClassifierService service = mTestWatcher.getService();
121         service.awaitQuery();
122 
123         service.resetRequestLatch(1);
124         secondSessionSession.classifyText(request);
125         service.awaitQuery();
126 
127         final List<TextClassificationSessionId> sessionIds =
128                 service.getRequestSessions().get("onClassifyText");
129         assertThat(sessionIds).hasSize(2);
130         assertThat(sessionIds.get(0).getValue()).isNotEqualTo(sessionIds.get(1).getValue());
131 
132         service.resetRequestLatch(2);
133         firstSession.destroy();
134         secondSessionSession.destroy();
135         service.awaitQuery();
136 
137         final List<TextClassificationSessionId> destroyedSessionIds =
138                 service.getRequestSessions().get("onDestroyTextClassificationSession");
139         assertThat(destroyedSessionIds).hasSize(2);
140         firstSession.isDestroyed();
141         secondSessionSession.isDestroyed();
142     }
143 
serviceOnSuggestConversationActions(CtsTextClassifierService service)144     private void serviceOnSuggestConversationActions(CtsTextClassifierService service)
145             throws Exception {
146         ConversationActions.Request conversationActionRequest =
147                 new ConversationActions.Request.Builder(Collections.emptyList()).build();
148         // TODO: add @TestApi for TextClassificationSessionId and use it
149         SelectionEvent event =
150                 SelectionEvent.createSelectionStartedEvent(
151                         SelectionEvent.INVOCATION_LINK, 1);
152         final CountDownLatch onSuccessLatch = new CountDownLatch(1);
153         service.onSuggestConversationActions(event.getSessionId(),
154                 conversationActionRequest, new CancellationSignal(),
155                 new TextClassifierService.Callback<ConversationActions>() {
156                     @Override
157                     public void onFailure(CharSequence charSequence) {
158                         // do nothing
159                     }
160 
161                     @Override
162                     public void onSuccess(ConversationActions o) {
163                         onSuccessLatch.countDown();
164                     }
165                 });
166         onSuccessLatch.await();
167         final List<TextClassificationSessionId> sessionIds =
168                 service.getRequestSessions().get("onSuggestConversationActions");
169         assertThat(sessionIds).hasSize(1);
170     }
171 
172     @Test
testTextClassifierServiceApiCoverage()173     public void testTextClassifierServiceApiCoverage() throws Exception {
174         // Implemented for API test coverage only
175         // Any method that is better tested not be called here.
176         // We already have tests for the TC APIs
177         // We however need to directly query the TextClassifierService methods in the tests for
178         // code coverage.
179         CtsTextClassifierService service = new CtsTextClassifierService();
180 
181         service.onConnected();
182 
183         serviceOnSuggestConversationActions(service);
184 
185         service.onDisconnected();
186     }
187 
188     @Test
testResourceIconsRewrittenToContentUriIcons_classifyText()189     public void testResourceIconsRewrittenToContentUriIcons_classifyText() throws Exception {
190         final TextClassifier tc = ApplicationProvider.getApplicationContext()
191                 .getSystemService(TextClassificationManager.class)
192                 .getTextClassifier();
193         final TextClassification.Request request =
194                 new TextClassification.Request.Builder("0800 123 4567", 0, 12).build();
195 
196         final TextClassification classification = tc.classifyText(request);
197         final Icon icon = classification.getActions().get(0).getIcon();
198         assertThat(icon.getType()).isEqualTo(Icon.TYPE_URI);
199         assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
200     }
201 
202     @Test
testResourceIconsRewrittenToContentUriIcons_suggestSelection()203     public void testResourceIconsRewrittenToContentUriIcons_suggestSelection() throws Exception {
204         assumeTrue(BuildCompat.isAtLeastS());
205 
206         final TextClassifier tc = ApplicationProvider.getApplicationContext()
207                 .getSystemService(TextClassificationManager.class)
208                 .getTextClassifier();
209         final TextSelection.Request request =
210                 new TextSelection.Request.Builder("0800 123 4567", 0, 12)
211                         .setIncludeTextClassification(true)
212                         .build();
213 
214         final TextSelection textSelection = tc.suggestSelection(request);
215         final Icon icon = textSelection.getTextClassification().getActions().get(0).getIcon();
216         assertThat(icon.getType()).isEqualTo(Icon.TYPE_URI);
217         assertThat(icon.getUri()).isEqualTo(CtsTextClassifierService.ICON_URI.getUri());
218     }
219 
220     /**
221      * Start an Activity from another package that queries the device's TextClassifierService when
222      * started and immediately terminates itself. When the Activity finishes, it sends broadcast, we
223      * check that whether the finish broadcast is received.
224      */
runQueryTextClassifierServiceActivity()225     private void runQueryTextClassifierServiceActivity() {
226         final String actionQueryActivityFinish =
227                 "ACTION_QUERY_SERVICE_ACTIVITY_FINISH_" + SystemClock.uptimeMillis();
228         final Context context = InstrumentationRegistry.getTargetContext();
229 
230         // register a activity finish receiver
231         final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context,
232                 actionQueryActivityFinish);
233         receiver.register();
234 
235         // Start an Activity from another package
236         final Intent outsideActivity = new Intent();
237         outsideActivity.setComponent(new ComponentName("android.textclassifier.cts2",
238                 "android.textclassifier.cts2.QueryTextClassifierServiceActivity"));
239         outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
240         final Intent broadcastIntent = new Intent(actionQueryActivityFinish);
241         final PendingIntent pendingIntent =
242                 PendingIntent.getBroadcast(
243                     context,
244                     0,
245                     broadcastIntent,
246                     PendingIntent.FLAG_IMMUTABLE);
247         outsideActivity.putExtra("finishBroadcast", pendingIntent);
248         context.startActivity(outsideActivity);
249 
250         TextClassifierTestWatcher.waitForIdle();
251 
252         // Verify the finish broadcast is received.
253         final Intent intent = receiver.awaitForBroadcast();
254         assertThat(intent).isNotNull();
255 
256         // unregister receiver
257         receiver.unregisterQuietly();
258     }
259 }
260