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     private static final Icon ICON_RES =
56             Icon.createWithResource("android", android.R.drawable.btn_star);
57     static final Icon ICON_URI =
58             Icon.createWithContentUri(new Uri.Builder()
59                   .scheme("content")
60                   .authority("com.android.textclassifier.icons")
61                   .path("android")
62                   .appendPath("" + android.R.drawable.btn_star)
63                   .build());
64 
65     private final Map<String, List<TextClassificationSessionId>> mRequestSessions =
66             new ArrayMap<>();
67     private final CountDownLatch mRequestLatch = new CountDownLatch(1);
68 
69     /**
70      * Returns the TestWatcher that was used for the testing.
71      */
72     @NonNull
getTestWatcher()73     public static TextClassifierTestWatcher getTestWatcher() {
74         return new TextClassifierTestWatcher();
75     }
76 
77     /**
78      * Returns all incoming session IDs, keyed by API name.
79      */
80     @NonNull
getRequestSessions()81     Map<String, List<TextClassificationSessionId>> getRequestSessions() {
82         return Collections.unmodifiableMap(mRequestSessions);
83     }
84 
awaitQuery(long timeoutMillis)85     void awaitQuery(long timeoutMillis) {
86         try {
87             mRequestLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
88         } catch (InterruptedException e) {
89             // do nothing
90         }
91     }
92 
93     @Override
onSuggestSelection(TextClassificationSessionId sessionId, TextSelection.Request request, CancellationSignal cancellationSignal, Callback<TextSelection> callback)94     public void onSuggestSelection(TextClassificationSessionId sessionId,
95             TextSelection.Request request, CancellationSignal cancellationSignal,
96             Callback<TextSelection> callback) {
97         handleRequest(sessionId, "onSuggestSelection");
98         TextSelection.Builder textSelection =
99                 new TextSelection.Builder(request.getStartIndex(), request.getEndIndex());
100         if (BuildCompat.isAtLeastS() && request.shouldIncludeTextClassification()) {
101             textSelection.setTextClassification(createTextClassification());
102         }
103         callback.onSuccess(textSelection.build());
104     }
105 
106     @Override
onClassifyText(TextClassificationSessionId sessionId, TextClassification.Request request, CancellationSignal cancellationSignal, Callback<TextClassification> callback)107     public void onClassifyText(TextClassificationSessionId sessionId,
108             TextClassification.Request request, CancellationSignal cancellationSignal,
109             Callback<TextClassification> callback) {
110         handleRequest(sessionId, "onClassifyText");
111         callback.onSuccess(createTextClassification());
112     }
113 
createTextClassification()114     private TextClassification createTextClassification() {
115         return new TextClassification.Builder()
116                 .addAction(new RemoteAction(
117                         ICON_RES,
118                         "Test Action",
119                         "Test Action",
120                         PendingIntent.getActivity(
121                                 this,
122                                 0,
123                                 new Intent(),
124                                 PendingIntent.FLAG_IMMUTABLE)))
125                 .build();
126     }
127 
128     @Override
onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request, CancellationSignal cancellationSignal, Callback<TextLinks> callback)129     public void onGenerateLinks(TextClassificationSessionId sessionId, TextLinks.Request request,
130             CancellationSignal cancellationSignal, Callback<TextLinks> callback) {
131         handleRequest(sessionId, "onGenerateLinks");
132         callback.onSuccess(TextClassifier.NO_OP.generateLinks(request));
133     }
134 
135     @Override
onDetectLanguage(TextClassificationSessionId sessionId, TextLanguage.Request request, CancellationSignal cancellationSignal, Callback<TextLanguage> callback)136     public void onDetectLanguage(TextClassificationSessionId sessionId,
137             TextLanguage.Request request, CancellationSignal cancellationSignal,
138             Callback<TextLanguage> callback) {
139         handleRequest(sessionId, "onDetectLanguage");
140         callback.onSuccess(TextClassifier.NO_OP.detectLanguage(request));
141     }
142 
143     @Override
onSuggestConversationActions(TextClassificationSessionId sessionId, ConversationActions.Request request, CancellationSignal cancellationSignal, Callback<ConversationActions> callback)144     public void onSuggestConversationActions(TextClassificationSessionId sessionId,
145             ConversationActions.Request request, CancellationSignal cancellationSignal,
146             Callback<ConversationActions> callback) {
147         handleRequest(sessionId, "onSuggestConversationActions");
148         callback.onSuccess(TextClassifier.NO_OP.suggestConversationActions(request));
149     }
150 
151     @Override
onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event)152     public void onSelectionEvent(TextClassificationSessionId sessionId, SelectionEvent event) {
153         handleRequest(sessionId, "onSelectionEvent");
154     }
155 
156     @Override
onTextClassifierEvent(TextClassificationSessionId sessionId, TextClassifierEvent event)157     public void onTextClassifierEvent(TextClassificationSessionId sessionId,
158             TextClassifierEvent event) {
159         handleRequest(sessionId, "onTextClassifierEvent");
160     }
161 
162     @Override
onCreateTextClassificationSession(TextClassificationContext context, TextClassificationSessionId sessionId)163     public void onCreateTextClassificationSession(TextClassificationContext context,
164             TextClassificationSessionId sessionId) {
165         handleRequest(sessionId, "onCreateTextClassificationSession");
166     }
167 
168     @Override
onDestroyTextClassificationSession(TextClassificationSessionId sessionId)169     public void onDestroyTextClassificationSession(TextClassificationSessionId sessionId) {
170         handleRequest(sessionId, "onDestroyTextClassificationSession");
171     }
172 
handleRequest(TextClassificationSessionId sessionId, String methodName)173     private void handleRequest(TextClassificationSessionId sessionId, String methodName) {
174         mRequestSessions.computeIfAbsent(methodName, key -> new ArrayList<>()).add(sessionId);
175         mRequestLatch.countDown();
176     }
177 
178     @Override
onConnected()179     public void onConnected() {
180         TextClassifierTestWatcher.ServiceWatcher.onConnected(/* service */ this);
181     }
182 
183     @Override
onDisconnected()184     public void onDisconnected() {
185         TextClassifierTestWatcher.ServiceWatcher.onDisconnected();
186     }
187 }
188