xref: /aosp_15_r20/cts/tests/tests/voiceRecognition/src/android/voicerecognition/cts/CtsRecognitionService.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2021 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 android.voicerecognition.cts;
18 
19 import static android.voicerecognition.cts.TestObjects.ERROR_CODE;
20 import static android.voicerecognition.cts.TestObjects.LANGUAGE_DETECTION_BUNDLE;
21 import static android.voicerecognition.cts.TestObjects.PARTIAL_RESULTS_BUNDLE;
22 import static android.voicerecognition.cts.TestObjects.READY_FOR_SPEECH_BUNDLE;
23 import static android.voicerecognition.cts.TestObjects.RESULTS_BUNDLE;
24 import static android.voicerecognition.cts.TestObjects.RMS_CHANGED_VALUE;
25 import static android.voicerecognition.cts.TestObjects.SEGMENT_RESULTS_BUNDLE;
26 
27 import static com.google.common.truth.Truth.assertThat;
28 
29 import static org.junit.Assert.fail;
30 
31 import android.content.AttributionSource;
32 import android.content.Intent;
33 import android.os.RemoteException;
34 import android.speech.ModelDownloadListener;
35 import android.speech.RecognitionService;
36 import android.speech.SpeechRecognizer;
37 import android.util.Log;
38 import android.util.Pair;
39 
40 import androidx.annotation.NonNull;
41 
42 import java.util.ArrayDeque;
43 import java.util.ArrayList;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Queue;
48 import java.util.Random;
49 import java.util.concurrent.atomic.AtomicBoolean;
50 import java.util.concurrent.atomic.AtomicInteger;
51 import java.util.function.Consumer;
52 
53 public class CtsRecognitionService extends RecognitionService {
54     private static final String TAG = CtsRecognitionService.class.getSimpleName();
55 
56     /** Map of the recognizer methods invoked, indexed by their id. */
57     public static Map<Integer, List<RecognizerMethod>> sInvokedRecognizerMethods = new HashMap<>();
58 
59     /**
60      * Queue of instructions for callbacks on main tasks ({@link
61      * CtsRecognitionService#onStartListening}, {@link CtsRecognitionService#onStopListening},
62      * {@link CtsRecognitionService#onCancel}). Each instruction is a pair
63      * consisting of a recognizer id and the callback to be run on the recognizer's listener.
64      */
65     public static Queue<Pair<Integer, CallbackMethod>> sInstructedCallbackMethods =
66             new ArrayDeque<>();
67 
68     /**
69      * Queue of instructions for callbacks on the model download task - {@link
70      * CtsRecognitionService#onTriggerModelDownload(
71      * Intent, AttributionSource, ModelDownloadListener)}.
72      *
73      * <p> Each instruction represents the callback to be run on the given listener.
74      */
75     public static Queue<ModelDownloadCallback> sInstructedModelDownloadCallbacks = null;
76 
77     public static AtomicBoolean sIsActive = new AtomicBoolean(false);
78     public static Queue<Consumer<SupportCallback>> sConsumerQueue = new ArrayDeque<>();
79     public static List<Intent> sDownloadTriggers = new ArrayList<>();
80 
81     public static AtomicInteger sBindCount = new AtomicInteger(0);
82 
83     static final int MAX_CONCURRENT_SESSIONS_COUNT = 3;
84 
85     private final Random mRandom = new Random();
86 
87     @Override
onStartListening(Intent recognizerIntent, Callback listener)88     protected void onStartListening(Intent recognizerIntent, Callback listener) {
89         sIsActive.set(true);
90         assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
91 
92         int recognizerId = processInstructedCallback(listener);
93         if (recognizerId >= 0) {
94             sInvokedRecognizerMethods.putIfAbsent(recognizerId, new ArrayList<>());
95             sInvokedRecognizerMethods.get(recognizerId)
96                     .add(RecognizerMethod.RECOGNIZER_METHOD_START_LISTENING);
97         }
98         sIsActive.set(false);
99     }
100 
101     @Override
onBindInternal()102     public void onBindInternal() {
103         Log.d(TAG, "onBind");
104         sBindCount.incrementAndGet();
105     }
106 
107     @Override
onUnbind(Intent intent)108     public boolean onUnbind(Intent intent) {
109         Log.d(TAG, "onUnbind");
110         sBindCount.decrementAndGet();
111         return super.onUnbind(intent);
112     }
113 
114     @Override
onStopListening(Callback listener)115     protected void onStopListening(Callback listener) {
116         sIsActive.set(true);
117         assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
118 
119         int recognizerId = processInstructedCallback(listener);
120         if (recognizerId >= 0) {
121             sInvokedRecognizerMethods.putIfAbsent(recognizerId, new ArrayList<>());
122             sInvokedRecognizerMethods.get(recognizerId)
123                     .add(RecognizerMethod.RECOGNIZER_METHOD_STOP_LISTENING);
124         }
125         sIsActive.set(false);
126     }
127 
128     @Override
onCheckRecognitionSupport( @onNull Intent recognizerIntent, @NonNull AttributionSource attributionSource, @NonNull SupportCallback supportCallback)129     public void onCheckRecognitionSupport(
130             @NonNull Intent recognizerIntent,
131             @NonNull AttributionSource attributionSource,
132             @NonNull SupportCallback supportCallback) {
133         Consumer<SupportCallback> consumer = sConsumerQueue.poll();
134         if (consumer == null) {
135             supportCallback.onError(SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT);
136         } else {
137             assertThat(attributionSource.getUid()).isEqualTo(android.os.Process.myUid());
138             consumer.accept(supportCallback);
139         }
140     }
141 
142     @Override
onTriggerModelDownload( @onNull Intent recognizerIntent, @NonNull AttributionSource attributionSource)143     public void onTriggerModelDownload(
144             @NonNull Intent recognizerIntent,
145             @NonNull AttributionSource attributionSource) {
146         assertThat(attributionSource.getUid()).isEqualTo(android.os.Process.myUid());
147         sDownloadTriggers.add(recognizerIntent);
148     }
149 
150     @Override
onTriggerModelDownload( @onNull Intent recognizerIntent, @NonNull AttributionSource attributionSource, @NonNull ModelDownloadListener listener)151     public void onTriggerModelDownload(
152             @NonNull Intent recognizerIntent,
153             @NonNull AttributionSource attributionSource,
154             @NonNull ModelDownloadListener listener) {
155         assertThat(attributionSource.getUid()).isEqualTo(android.os.Process.myUid());
156 
157         while (!sInstructedModelDownloadCallbacks.isEmpty()) {
158             ModelDownloadCallback callback = sInstructedModelDownloadCallbacks.poll();
159             switch (callback) {
160                 case ON_PROGRESS:
161                     listener.onProgress(50);
162                     break;
163                 case ON_SUCCESS:
164                     listener.onSuccess();
165                     break;
166                 case ON_SCHEDULED:
167                     listener.onScheduled();
168                     break;
169                 case ON_ERROR:
170                     listener.onError(0);
171                     break;
172                 default:
173                     break;
174             }
175         }
176         sDownloadTriggers.add(recognizerIntent);
177     }
178 
179     @Override
onCancel(Callback listener)180     protected void onCancel(Callback listener) {
181         sIsActive.set(true);
182         assertThat(listener.getCallingUid()).isEqualTo(android.os.Process.myUid());
183 
184         int recognizerId = processInstructedCallback(listener);
185         if (recognizerId >= 0) {
186             sInvokedRecognizerMethods.putIfAbsent(recognizerId, new ArrayList<>());
187             sInvokedRecognizerMethods.get(recognizerId).add(
188                     RecognizerMethod.RECOGNIZER_METHOD_CANCEL);
189         }
190         sIsActive.set(false);
191     }
192 
193     @Override
getMaxConcurrentSessionsCount()194     public int getMaxConcurrentSessionsCount() {
195         return MAX_CONCURRENT_SESSIONS_COUNT;
196     }
197 
198     /**
199      * Process the next callback instruction in the queue by callback on the given listener.
200      * Return the id of the corresponding recognizer object.
201      *
202      * @param listener listener on which the callback should be invoked
203      * @return the id of the corresponding recognizer
204      */
processInstructedCallback(Callback listener)205     private int processInstructedCallback(Callback listener) {
206         if (sInstructedCallbackMethods.isEmpty()) {
207             return -1;
208         }
209 
210         Pair<Integer, CallbackMethod> callbackInstruction = sInstructedCallbackMethods.poll();
211         int recognizerId = callbackInstruction.first;
212         CallbackMethod callbackMethod = callbackInstruction.second;
213 
214         Log.i(TAG, "Responding with " + callbackMethod.name() + ".");
215 
216         try {
217             switch (callbackMethod) {
218                 case CALLBACK_METHOD_UNSPECIFIED:
219                     // ignore
220                     break;
221                 case CALLBACK_METHOD_BEGINNING_OF_SPEECH:
222                     listener.beginningOfSpeech();
223                     break;
224                 case CALLBACK_METHOD_BUFFER_RECEIVED:
225                     byte[] buffer = new byte[100];
226                     mRandom.nextBytes(buffer);
227                     listener.bufferReceived(buffer);
228                     break;
229                 case CALLBACK_METHOD_END_OF_SPEECH:
230                     listener.endOfSpeech();
231                     break;
232                 case CALLBACK_METHOD_ERROR:
233                     listener.error(ERROR_CODE);
234                     break;
235                 case CALLBACK_METHOD_RESULTS:
236                     listener.results(RESULTS_BUNDLE);
237                     break;
238                 case CALLBACK_METHOD_PARTIAL_RESULTS:
239                     listener.partialResults(PARTIAL_RESULTS_BUNDLE);
240                     break;
241                 case CALLBACK_METHOD_READY_FOR_SPEECH:
242                     listener.readyForSpeech(READY_FOR_SPEECH_BUNDLE);
243                     break;
244                 case CALLBACK_METHOD_RMS_CHANGED:
245                     listener.rmsChanged(RMS_CHANGED_VALUE);
246                     break;
247                 case CALLBACK_METHOD_SEGMENTS_RESULTS:
248                     listener.segmentResults(SEGMENT_RESULTS_BUNDLE);
249                     break;
250                 case CALLBACK_METHOD_END_SEGMENTED_SESSION:
251                     listener.endOfSegmentedSession();
252                     break;
253                 case CALLBACK_METHOD_LANGUAGE_DETECTION:
254                     listener.languageDetection(LANGUAGE_DETECTION_BUNDLE);
255                     break;
256                 default:
257                     fail();
258             }
259         } catch (RemoteException e) {
260             fail();
261         }
262 
263         return recognizerId;
264     }
265 
totalInvokedRecognizerMethodsCount()266     static int totalInvokedRecognizerMethodsCount() {
267         return sInvokedRecognizerMethods.values().stream().mapToInt(List::size).sum();
268     }
269 }
270