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 android.app.PendingIntent;
19 import android.app.RemoteAction;
20 import android.content.Intent;
21 import android.graphics.drawable.Icon;
22 import android.net.Uri;
23 import android.os.CancellationSignal;
24 import android.service.textclassifier.TextClassifierService;
25 import android.util.ArrayMap;
26 import android.view.textclassifier.ConversationActions;
27 import android.view.textclassifier.SelectionEvent;
28 import android.view.textclassifier.TextClassification;
29 import android.view.textclassifier.TextClassificationContext;
30 import android.view.textclassifier.TextClassificationSessionId;
31 import android.view.textclassifier.TextClassifier;
32 import android.view.textclassifier.TextClassifierEvent;
33 import android.view.textclassifier.TextLanguage;
34 import android.view.textclassifier.TextLinks;
35 import android.view.textclassifier.TextSelection;
36 
37 import androidx.core.os.BuildCompat;
38 import androidx.annotation.NonNull;
39 
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.concurrent.CountDownLatch;
45 import java.util.concurrent.TimeUnit;
46 
47 /**
48  * Implementation of {@link TextClassifierService} used in the tests.
49  */
50 public final class CtsTextClassifierService extends TextClassifierService {
51 
52     private static final String TAG = "CtsTextClassifierService";
53     public static final String MY_PACKAGE = "android.view.textclassifier.cts";
54 
55     public static final int DEFAULT_API_WAIT_TIMEOUT = 1_000;
56 
57     private static final Icon ICON_RES =
58             Icon.createWithResource("android", android.R.drawable.btn_star);
59     static final Icon ICON_URI =
60             Icon.createWithContentUri(new Uri.Builder()
61                   .scheme("content")
62                   .authority("com.android.textclassifier.icons")
63                   .path("android")
64                   .appendPath("" + android.R.drawable.btn_star)
65                   .build());
66 
67     private final Map<String, List<TextClassificationSessionId>> mRequestSessions =
68             new ArrayMap<>();
69     private CountDownLatch mRequestLatch = new CountDownLatch(1);
70 
71     /**
72      * Returns the TestWatcher that was used for the testing.
73      */
74     @NonNull
getTestWatcher()75     public static TextClassifierTestWatcher getTestWatcher() {
76         return new TextClassifierTestWatcher();
77     }
78 
79     /**
80      * Returns all incoming session IDs, keyed by API name.
81      */
82     @NonNull
getRequestSessions()83     Map<String, List<TextClassificationSessionId>> getRequestSessions() {
84         return Collections.unmodifiableMap(mRequestSessions);
85     }
86 
resetRequestLatch(int count)87     void resetRequestLatch(int count) {
88         mRequestLatch = new CountDownLatch(count);
89     }
90 
awaitQuery()91     void awaitQuery() {
92         awaitQuery(DEFAULT_API_WAIT_TIMEOUT);
93     }
94 
awaitQuery(long timeoutMillis)95     void awaitQuery(long timeoutMillis) {
96         try {
97             mRequestLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
98         } catch (InterruptedException e) {
99             // do nothing
100         }
101     }
102 
103     @Override
onSuggestSelection(TextClassificationSessionId sessionId, TextSelection.Request request, CancellationSignal cancellationSignal, Callback<TextSelection> callback)104     public void onSuggestSelection(TextClassificationSessionId sessionId,
105             TextSelection.Request request, CancellationSignal cancellationSignal,
106             Callback<TextSelection> callback) {
107         handleRequest(sessionId, "onSuggestSelection");
108         TextSelection.Builder textSelection =
109                 new TextSelection.Builder(request.getStartIndex(), request.getEndIndex());
110         if (BuildCompat.isAtLeastS() && request.shouldIncludeTextClassification()) {
111             textSelection.setTextClassification(createTextClassification());
112         }
113         callback.onSuccess(textSelection.build());
114     }
115 
116     @Override
onClassifyText(TextClassificationSessionId sessionId, TextClassification.Request request, CancellationSignal cancellationSignal, Callback<TextClassification> callback)117     public void onClassifyText(TextClassificationSessionId sessionId,
118             TextClassification.Request request, CancellationSignal cancellationSignal,
119             Callback<TextClassification> callback) {
120         handleRequest(sessionId, "onClassifyText");
121         callback.onSuccess(createTextClassification());
122     }
123 
createTextClassification()124     private TextClassification createTextClassification() {
125         return new TextClassification.Builder()
126                 .addAction(new RemoteAction(
127                         ICON_RES,
128                         "Test Action",
129                         "Test Action",
130                         PendingIntent.getActivity(
131                                 this,
132                                 0,
133                                 new Intent(),
134                                 PendingIntent.FLAG_IMMUTABLE)))
135                 .build();
136     }
137 
138     @Override
onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request, CancellationSignal cancellationSignal, Callback<TextLinks> callback)139     public void onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request,
140             CancellationSignal cancellationSignal, Callback<TextLinks> callback) {
141         handleRequest(sessionId, "onGenerateLinks");
142         callback.onSuccess(TextClassifier.NO_OP.generateLinks(request));
143     }
144 
145     @Override
onDetectLanguage(TextClassificationSessionId sessionId, TextLanguage.Request request, CancellationSignal cancellationSignal, Callback<TextLanguage> callback)146     public void onDetectLanguage(TextClassificationSessionId sessionId,
147             TextLanguage.Request request, CancellationSignal cancellationSignal,
148             Callback<TextLanguage> callback) {
149         handleRequest(sessionId, "onDetectLanguage");
150         callback.onSuccess(TextClassifier.NO_OP.detectLanguage(request));
151     }
152 
153     @Override
onSuggestConversationActions(TextClassificationSessionId sessionId, ConversationActions.Request request, CancellationSignal cancellationSignal, Callback<ConversationActions> callback)154     public void onSuggestConversationActions(TextClassificationSessionId sessionId,
155             ConversationActions.Request request, CancellationSignal cancellationSignal,
156             Callback<ConversationActions> callback) {
157         handleRequest(sessionId, "onSuggestConversationActions");
158         callback.onSuccess(TextClassifier.NO_OP.suggestConversationActions(request));
159     }
160 
161     @Override
onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event)162     public void onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event) {
163         handleRequest(sessionId, "onSelectionEvent");
164     }
165 
166     @Override
onTextClassifierEvent(TextClassificationSessionId sessionId, TextClassifierEvent event)167     public void onTextClassifierEvent(TextClassificationSessionId sessionId,
168             TextClassifierEvent event) {
169         handleRequest(sessionId, "onTextClassifierEvent");
170     }
171 
172     @Override
onCreateTextClassificationSession(TextClassificationContext context, TextClassificationSessionId sessionId)173     public void onCreateTextClassificationSession(TextClassificationContext context,
174             TextClassificationSessionId sessionId) {
175         handleRequest(sessionId, "onCreateTextClassificationSession");
176     }
177 
178     @Override
onDestroyTextClassificationSession(TextClassificationSessionId sessionId)179     public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
180         handleRequest(sessionId, "onDestroyTextClassificationSession");
181     }
182 
handleRequest(TextClassificationSessionId sessionId, String methodName)183     private void handleRequest(TextClassificationSessionId sessionId, String methodName) {
184         mRequestSessions.computeIfAbsent(methodName, key -> new ArrayList<>()).add(sessionId);
185         mRequestLatch.countDown();
186     }
187 
188     @Override
onConnected()189     public void onConnected() {
190         TextClassifierTestWatcher.ServiceWatcher.onConnected(/* service */ this);
191     }
192 
193     @Override
onDisconnected()194     public void onDisconnected() {
195         TextClassifierTestWatcher.ServiceWatcher.onDisconnected();
196     }
197 }
198