1 /*
2  * Copyright (C) 2010 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.speech;
18 
19 import android.Manifest;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.MainThread;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.os.Binder;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.provider.Settings;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.util.Slog;
40 
41 import java.util.Objects;
42 import java.util.Queue;
43 import java.util.concurrent.Executor;
44 import java.util.concurrent.LinkedBlockingQueue;
45 
46 /**
47  * @hide
48  */
49 class SpeechRecognizerImpl extends SpeechRecognizer {
50     /** DEBUG value to enable verbose debug prints */
51     private static final boolean DBG = false;
52 
53     /** Log messages identifier */
54     private static final String TAG = "SpeechRecognizer";
55 
56     /** action codes */
57     private static final int MSG_START = 1;
58     private static final int MSG_STOP = 2;
59     private static final int MSG_CANCEL = 3;
60     private static final int MSG_CHANGE_LISTENER = 4;
61     private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
62     private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
63     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
64     private static final int MSG_DESTROY = 8;
65 
66     /** The actual RecognitionService endpoint */
67     private IRecognitionService mService;
68 
69     /** Context with which the manager was created */
70     private final Context mContext;
71 
72     /** Component to direct service intent to */
73     private final ComponentName mServiceComponent;
74 
75     /** Whether to use on-device speech recognizer. */
76     private final boolean mOnDevice;
77 
78     private IRecognitionServiceManager mManagerService;
79 
80     /** Handler that will execute the main tasks */
81     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
82 
83         @Override
84         public void handleMessage(Message msg) {
85             switch (msg.what) {
86                 case MSG_START -> handleStartListening((Intent) msg.obj);
87                 case MSG_STOP -> handleStopMessage();
88                 case MSG_CANCEL -> handleCancelMessage();
89                 case MSG_CHANGE_LISTENER -> handleChangeListener((RecognitionListener) msg.obj);
90                 case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT ->
91                         handleSetTemporaryComponent((ComponentName) msg.obj);
92                 case MSG_CHECK_RECOGNITION_SUPPORT -> {
93                     CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj;
94                     handleCheckRecognitionSupport(
95                             args.mIntent, args.mCallbackExecutor, args.mCallback);
96                 }
97                 case MSG_TRIGGER_MODEL_DOWNLOAD -> {
98                     ModelDownloadListenerArgs modelDownloadListenerArgs =
99                             (ModelDownloadListenerArgs) msg.obj;
100                     handleTriggerModelDownload(
101                             modelDownloadListenerArgs.mIntent,
102                             modelDownloadListenerArgs.mExecutor,
103                             modelDownloadListenerArgs.mModelDownloadListener);
104                 }
105                 case MSG_DESTROY -> handleDestroy();
106             }
107         }
108     };
109 
110     /**
111      * Temporary queue, saving the messages until the connection will be established, afterwards,
112      * only mHandler will receive the messages
113      */
114     private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>();
115 
116     /** The Listener that will receive all the callbacks */
117     private final InternalRecognitionListener mListener = new InternalRecognitionListener();
118 
119     private final IBinder mClientToken = new Binder();
120 
121     /**
122      * The right way to create a {@code SpeechRecognizer} is by using
123      * {@link #createSpeechRecognizer} static factory method
124      */
SpeechRecognizerImpl( final Context context, final ComponentName serviceComponent)125     /* package */ SpeechRecognizerImpl(
126             final Context context,
127             final ComponentName serviceComponent) {
128         this(context, serviceComponent, false);
129     }
130 
131     /**
132      * The right way to create a {@code SpeechRecognizer} is by using
133      * {@link #createOnDeviceSpeechRecognizer} static factory method
134      */
SpeechRecognizerImpl(final Context context, boolean onDevice)135     /* package */ SpeechRecognizerImpl(final Context context, boolean onDevice) {
136         this(context, null, onDevice);
137     }
138 
SpeechRecognizerImpl( final Context context, final ComponentName serviceComponent, final boolean onDevice)139     private SpeechRecognizerImpl(
140             final Context context,
141             final ComponentName serviceComponent,
142             final boolean onDevice) {
143         mContext = context;
144         mServiceComponent = serviceComponent;
145         mOnDevice = onDevice;
146     }
147 
148     @NonNull
149     @MainThread
lenientlyCreateOnDeviceSpeechRecognizer( @onNull final Context context)150     /* package */ static SpeechRecognizerImpl lenientlyCreateOnDeviceSpeechRecognizer(
151             @NonNull final Context context) {
152         if (context == null) {
153             throw new IllegalArgumentException("Context cannot be null");
154         }
155         checkIsCalledFromMainThread();
156         return new SpeechRecognizerImpl(context, /* onDevice */ true);
157     }
158 
159     @Override
160     @MainThread
setRecognitionListener(RecognitionListener listener)161     public void setRecognitionListener(RecognitionListener listener) {
162         checkIsCalledFromMainThread();
163         if (mListener.mInternalListener == null) {
164             // This shortcut is needed because otherwise, if there's an error connecting, it never
165             // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does
166             // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same
167             // place does not get forwarded anywhere.
168             // Thread-wise, this is safe as both this method and the handler are on the UI thread.
169             handleChangeListener(listener);
170         } else {
171             putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
172         }
173     }
174 
175     @Override
176     @MainThread
startListening(final Intent recognizerIntent)177     public void startListening(final Intent recognizerIntent) {
178         if (recognizerIntent == null) {
179             throw new IllegalArgumentException("intent must not be null");
180         }
181         checkIsCalledFromMainThread();
182 
183         if (DBG) {
184             Slog.i(TAG, "#startListening called");
185             if (mService == null) {
186                 Slog.i(TAG, "Connection is not established yet");
187             }
188         }
189 
190         if (mService == null) {
191             // First time connection: first establish a connection, then dispatch #startListening.
192             connectToSystemService();
193         }
194         putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
195     }
196 
197     @Override
198     @MainThread
stopListening()199     public void stopListening() {
200         checkIsCalledFromMainThread();
201 
202         if (DBG) {
203             Slog.i(TAG, "#stopListening called");
204             if (mService == null) {
205                 Slog.i(TAG, "Connection is not established yet");
206             }
207         }
208 
209         putMessage(Message.obtain(mHandler, MSG_STOP));
210     }
211 
212     @Override
213     @MainThread
cancel()214     public void cancel() {
215         checkIsCalledFromMainThread();
216         putMessage(Message.obtain(mHandler, MSG_CANCEL));
217     }
218 
219     @Override
checkRecognitionSupport( @onNull Intent recognizerIntent, @NonNull @CallbackExecutor Executor executor, @NonNull RecognitionSupportCallback supportListener)220     public void checkRecognitionSupport(
221             @NonNull Intent recognizerIntent,
222             @NonNull @CallbackExecutor Executor executor,
223             @NonNull RecognitionSupportCallback supportListener) {
224         Objects.requireNonNull(recognizerIntent, "intent must not be null");
225         Objects.requireNonNull(supportListener, "listener must not be null");
226 
227         if (DBG) {
228             Slog.i(TAG, "#checkRecognitionSupport called");
229             if (mService == null) {
230                 Slog.i(TAG, "Connection is not established yet");
231             }
232         }
233 
234         if (mService == null) {
235             // First time connection: first establish a connection, then dispatch.
236             connectToSystemService();
237         }
238         putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT,
239                 new CheckRecognitionSupportArgs(recognizerIntent, executor, supportListener)));
240     }
241 
242     @Override
triggerModelDownload(@onNull Intent recognizerIntent)243     public void triggerModelDownload(@NonNull Intent recognizerIntent) {
244         Objects.requireNonNull(recognizerIntent, "intent must not be null");
245         if (DBG) {
246             Slog.i(TAG, "#triggerModelDownload without a listener called");
247             if (mService == null) {
248                 Slog.i(TAG, "Connection is not established yet");
249             }
250         }
251         if (mService == null) {
252             // First time connection: first establish a connection, then dispatch.
253             connectToSystemService();
254         }
255         putMessage(Message.obtain(
256                 mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
257                 new ModelDownloadListenerArgs(recognizerIntent, null, null)));
258     }
259 
260     @Override
triggerModelDownload( @onNull Intent recognizerIntent, @NonNull @CallbackExecutor Executor executor, @NonNull ModelDownloadListener listener)261     public void triggerModelDownload(
262             @NonNull Intent recognizerIntent,
263             @NonNull @CallbackExecutor Executor executor,
264             @NonNull ModelDownloadListener listener) {
265         Objects.requireNonNull(recognizerIntent, "intent must not be null");
266         if (DBG) {
267             Slog.i(TAG, "#triggerModelDownload with a listener called");
268             if (mService == null) {
269                 Slog.i(TAG, "Connection is not established yet");
270             }
271         }
272         if (mService == null) {
273             // First time connection: first establish a connection, then dispatch.
274             connectToSystemService();
275         }
276         putMessage(Message.obtain(
277                 mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
278                 new ModelDownloadListenerArgs(recognizerIntent, executor, listener)));
279     }
280 
281     @Override
282     @RequiresPermission(Manifest.permission.MANAGE_SPEECH_RECOGNITION)
setTemporaryOnDeviceRecognizer(@ullable ComponentName componentName)283     public void setTemporaryOnDeviceRecognizer(@Nullable ComponentName componentName) {
284         mHandler.sendMessage(
285                 Message.obtain(mHandler, MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT, componentName));
286     }
287 
checkIsCalledFromMainThread()288     /* package */ static void checkIsCalledFromMainThread() {
289         if (Looper.myLooper() != Looper.getMainLooper()) {
290             throw new RuntimeException(
291                     "SpeechRecognizer should be used only from the application's main thread");
292         }
293     }
294 
putMessage(Message msg)295     private void putMessage(Message msg) {
296         if (mService == null) {
297             mPendingTasks.offer(msg);
298         } else {
299             mHandler.sendMessage(msg);
300         }
301     }
302 
303     /** sends the actual message to the service */
handleStartListening(Intent recognizerIntent)304     private void handleStartListening(Intent recognizerIntent) {
305         if (!checkOpenConnection()) {
306             return;
307         }
308         try {
309             mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource());
310             if (DBG) Log.d(TAG, "service start listening command succeeded");
311         } catch (final Exception e) {
312             Log.e(TAG, "startListening() failed", e);
313             mListener.onError(ERROR_CLIENT);
314         }
315     }
316 
317     /** sends the actual message to the service */
handleStopMessage()318     private void handleStopMessage() {
319         if (!checkOpenConnection()) {
320             return;
321         }
322         try {
323             mService.stopListening(mListener);
324             if (DBG) Log.d(TAG, "service stop listening command succeeded");
325         } catch (final Exception e) {
326             Log.e(TAG, "stopListening() failed", e);
327             mListener.onError(ERROR_CLIENT);
328         }
329     }
330 
331     /** sends the actual message to the service */
handleCancelMessage()332     private void handleCancelMessage() {
333         if (!checkOpenConnection()) {
334             return;
335         }
336         try {
337             mService.cancel(mListener, /*isShutdown*/ false);
338             if (DBG) Log.d(TAG, "service cancel command succeeded");
339         } catch (final Exception e) {
340             Log.e(TAG, "cancel() failed", e);
341             mListener.onError(ERROR_CLIENT);
342         }
343     }
344 
handleSetTemporaryComponent(ComponentName componentName)345     private void handleSetTemporaryComponent(ComponentName componentName) {
346         if (DBG) {
347             Log.d(TAG, "handleSetTemporaryComponent, componentName=" + componentName);
348         }
349 
350         if (!maybeInitializeManagerService()) {
351             return;
352         }
353 
354         try {
355             mManagerService.setTemporaryComponent(componentName);
356         } catch (final RemoteException e) {
357             e.rethrowFromSystemServer();
358         }
359     }
360 
handleCheckRecognitionSupport( Intent recognizerIntent, Executor callbackExecutor, RecognitionSupportCallback recognitionSupportCallback)361     private void handleCheckRecognitionSupport(
362             Intent recognizerIntent,
363             Executor callbackExecutor,
364             RecognitionSupportCallback recognitionSupportCallback) {
365         if (!maybeInitializeManagerService() || !checkOpenConnection()) {
366             return;
367         }
368         try {
369             mService.checkRecognitionSupport(
370                     recognizerIntent,
371                     mContext.getAttributionSource(),
372                     new InternalSupportCallback(callbackExecutor, recognitionSupportCallback));
373             if (DBG) Log.d(TAG, "service support command succeeded");
374         } catch (final Exception e) {
375             Log.e(TAG, "checkRecognitionSupport() failed", e);
376             callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT));
377         }
378     }
379 
handleTriggerModelDownload( Intent recognizerIntent, @Nullable Executor callbackExecutor, @Nullable ModelDownloadListener modelDownloadListener)380     private void handleTriggerModelDownload(
381             Intent recognizerIntent,
382             @Nullable Executor callbackExecutor,
383             @Nullable ModelDownloadListener modelDownloadListener) {
384         if (!maybeInitializeManagerService() || !checkOpenConnection()) {
385             return;
386         }
387 
388         if (modelDownloadListener == null) {
389             // Trigger model download without a listener.
390             try {
391                 mService.triggerModelDownload(
392                         recognizerIntent, mContext.getAttributionSource(), null);
393                 if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
394             } catch (final Exception e) {
395                 Log.e(TAG, "triggerModelDownload() without a listener failed", e);
396                 mListener.onError(ERROR_CLIENT);
397             }
398         } else {
399             // Trigger model download with a listener.
400             try {
401                 mService.triggerModelDownload(
402                         recognizerIntent, mContext.getAttributionSource(),
403                         new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
404                 if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
405             } catch (final Exception e) {
406                 Log.e(TAG, "triggerModelDownload() with a listener failed", e);
407                 callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
408             }
409         }
410     }
411 
checkOpenConnection()412     private boolean checkOpenConnection() {
413         if (mService != null && mService.asBinder().isBinderAlive()) {
414             return true;
415         }
416         mListener.onError(ERROR_CLIENT);
417         Log.e(TAG, "not connected to the recognition service");
418         return false;
419     }
420 
421     /** changes the listener */
handleChangeListener(RecognitionListener listener)422     private void handleChangeListener(RecognitionListener listener) {
423         if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
424         mListener.mInternalListener = listener;
425     }
426 
427     @Override
destroy()428     public void destroy() {
429         putMessage(mHandler.obtainMessage(MSG_DESTROY));
430     }
431 
handleDestroy()432     private void handleDestroy() {
433         if (mService != null) {
434             try {
435                 mService.cancel(mListener, /*isShutdown*/ true);
436             } catch (final Exception e) {
437                 // Not important
438             }
439         }
440 
441         mService = null;
442         mPendingTasks.clear();
443         mListener.mInternalListener = null;
444     }
445 
446     /** Establishes a connection to system server proxy and initializes the session. */
connectToSystemService()447     private void connectToSystemService() {
448         if (!maybeInitializeManagerService()) {
449             return;
450         }
451 
452         ComponentName componentName = getSpeechRecognizerComponentName();
453 
454         if (!mOnDevice && componentName == null) {
455             mListener.onError(ERROR_CLIENT);
456             return;
457         }
458 
459         try {
460             mManagerService.createSession(
461                     componentName,
462                     mClientToken,
463                     mOnDevice,
464                     new IRecognitionServiceManagerCallback.Stub(){
465                         @Override
466                         public void onSuccess(IRecognitionService service) throws RemoteException {
467                             if (DBG) {
468                                 Log.i(TAG, "Connected to speech recognition service");
469                             }
470                             mService = service;
471                             while (!mPendingTasks.isEmpty()) {
472                                 mHandler.sendMessage(mPendingTasks.poll());
473                             }
474                         }
475 
476                         @Override
477                         public void onError(int errorCode) throws RemoteException {
478                             Log.e(TAG, "Bind to system recognition service failed with error "
479                                     + errorCode);
480                             mListener.onError(errorCode);
481                         }
482                     });
483         } catch (RemoteException e) {
484             e.rethrowFromSystemServer();
485         }
486     }
487 
maybeInitializeManagerService()488     private synchronized boolean maybeInitializeManagerService() {
489         if (DBG) {
490             Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService);
491         }
492         if (mManagerService != null) {
493             return true;
494         }
495 
496         IBinder service = ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE);
497         if (service == null && mOnDevice) {
498             service = (IBinder) mContext.getSystemService(Context.SPEECH_RECOGNITION_SERVICE);
499         }
500         mManagerService = IRecognitionServiceManager.Stub.asInterface(service);
501 
502         if (mManagerService == null) {
503             if (mListener != null) {
504                 mListener.onError(ERROR_CLIENT);
505             }
506             return false;
507         }
508         return true;
509     }
510 
511     /**
512      * Returns the component name to be used for establishing a connection, based on the parameters
513      * used during initialization.
514      *
515      * <p>Note the 3 different scenarios:
516      * <ol>
517      *     <li>On-device speech recognizer which is determined by the manufacturer and not
518      *     changeable by the user
519      *     <li>Default user-selected speech recognizer as specified by
520      *     {@code Settings.Secure.VOICE_RECOGNITION_SERVICE}
521      *     <li>Custom speech recognizer supplied by the client.
522      * </ol>
523      */
524     @SuppressWarnings("NonUserGetterCalled")
getSpeechRecognizerComponentName()525     private ComponentName getSpeechRecognizerComponentName() {
526         if (mOnDevice) {
527             return null;
528         }
529 
530         if (mServiceComponent != null) {
531             return mServiceComponent;
532         }
533 
534         String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
535                 Settings.Secure.VOICE_RECOGNITION_SERVICE);
536 
537         if (TextUtils.isEmpty(serviceComponent)) {
538             Log.e(TAG, "no selected voice recognition service");
539             mListener.onError(ERROR_CLIENT);
540             return null;
541         }
542 
543         return ComponentName.unflattenFromString(serviceComponent);
544     }
545 
546     private static class CheckRecognitionSupportArgs {
547         final Intent mIntent;
548         final Executor mCallbackExecutor;
549         final RecognitionSupportCallback mCallback;
550 
CheckRecognitionSupportArgs( Intent intent, Executor callbackExecutor, RecognitionSupportCallback callback)551         private CheckRecognitionSupportArgs(
552                 Intent intent,
553                 Executor callbackExecutor,
554                 RecognitionSupportCallback callback) {
555             mIntent = intent;
556             mCallbackExecutor = callbackExecutor;
557             mCallback = callback;
558         }
559     }
560 
561     private static class ModelDownloadListenerArgs {
562         final Intent mIntent;
563         final Executor mExecutor;
564         final ModelDownloadListener mModelDownloadListener;
565 
ModelDownloadListenerArgs(Intent intent, Executor executor, ModelDownloadListener modelDownloadListener)566         private ModelDownloadListenerArgs(Intent intent, Executor executor,
567                 ModelDownloadListener modelDownloadListener) {
568             mIntent = intent;
569             mExecutor = executor;
570             mModelDownloadListener = modelDownloadListener;
571         }
572     }
573 
574     /**
575      * Internal wrapper of IRecognitionListener which will propagate the results to
576      * RecognitionListener
577      */
578     private static class InternalRecognitionListener extends IRecognitionListener.Stub {
579         private RecognitionListener mInternalListener;
580 
581         private static final int MSG_BEGINNING_OF_SPEECH = 1;
582         private static final int MSG_BUFFER_RECEIVED = 2;
583         private static final int MSG_END_OF_SPEECH = 3;
584         private static final int MSG_ERROR = 4;
585         private static final int MSG_READY_FOR_SPEECH = 5;
586         private static final int MSG_RESULTS = 6;
587         private static final int MSG_PARTIAL_RESULTS = 7;
588         private static final int MSG_RMS_CHANGED = 8;
589         private static final int MSG_ON_EVENT = 9;
590         private static final int MSG_SEGMENT_RESULTS = 10;
591         private static final int MSG_SEGMENT_END_SESSION = 11;
592         private static final int MSG_LANGUAGE_DETECTION = 12;
593 
594         private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) {
595             @Override
596             public void handleMessage(Message msg) {
597                 if (mInternalListener == null) {
598                     return;
599                 }
600                 switch (msg.what) {
601                     case MSG_BEGINNING_OF_SPEECH:
602                         mInternalListener.onBeginningOfSpeech();
603                         break;
604                     case MSG_BUFFER_RECEIVED:
605                         mInternalListener.onBufferReceived((byte[]) msg.obj);
606                         break;
607                     case MSG_END_OF_SPEECH:
608                         mInternalListener.onEndOfSpeech();
609                         break;
610                     case MSG_ERROR:
611                         mInternalListener.onError((Integer) msg.obj);
612                         break;
613                     case MSG_READY_FOR_SPEECH:
614                         mInternalListener.onReadyForSpeech((Bundle) msg.obj);
615                         break;
616                     case MSG_RESULTS:
617                         mInternalListener.onResults((Bundle) msg.obj);
618                         break;
619                     case MSG_PARTIAL_RESULTS:
620                         mInternalListener.onPartialResults((Bundle) msg.obj);
621                         break;
622                     case MSG_RMS_CHANGED:
623                         mInternalListener.onRmsChanged((Float) msg.obj);
624                         break;
625                     case MSG_ON_EVENT:
626                         mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
627                         break;
628                     case MSG_SEGMENT_RESULTS:
629                         mInternalListener.onSegmentResults((Bundle) msg.obj);
630                         break;
631                     case MSG_SEGMENT_END_SESSION:
632                         mInternalListener.onEndOfSegmentedSession();
633                         break;
634                     case MSG_LANGUAGE_DETECTION:
635                         mInternalListener.onLanguageDetection((Bundle) msg.obj);
636                         break;
637                 }
638             }
639         };
640 
onBeginningOfSpeech()641         public void onBeginningOfSpeech() {
642             Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
643         }
644 
onBufferReceived(final byte[] buffer)645         public void onBufferReceived(final byte[] buffer) {
646             Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
647         }
648 
onEndOfSpeech()649         public void onEndOfSpeech() {
650             Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
651         }
652 
onError(final int error)653         public void onError(final int error) {
654             Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
655         }
656 
onReadyForSpeech(final Bundle noiseParams)657         public void onReadyForSpeech(final Bundle noiseParams) {
658             Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
659         }
660 
onResults(final Bundle results)661         public void onResults(final Bundle results) {
662             Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
663         }
664 
onPartialResults(final Bundle results)665         public void onPartialResults(final Bundle results) {
666             Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
667         }
668 
onRmsChanged(final float rmsdB)669         public void onRmsChanged(final float rmsdB) {
670             Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
671         }
672 
onSegmentResults(final Bundle bundle)673         public void onSegmentResults(final Bundle bundle) {
674             Message.obtain(mInternalHandler, MSG_SEGMENT_RESULTS, bundle).sendToTarget();
675         }
676 
onEndOfSegmentedSession()677         public void onEndOfSegmentedSession() {
678             Message.obtain(mInternalHandler, MSG_SEGMENT_END_SESSION).sendToTarget();
679         }
680 
onLanguageDetection(final Bundle results)681         public void onLanguageDetection(final Bundle results) {
682             Message.obtain(mInternalHandler, MSG_LANGUAGE_DETECTION, results).sendToTarget();
683         }
684 
onEvent(final int eventType, final Bundle params)685         public void onEvent(final int eventType, final Bundle params) {
686             Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
687                     .sendToTarget();
688         }
689     }
690 
691     private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub {
692         private final Executor mExecutor;
693         private final RecognitionSupportCallback mCallback;
694 
InternalSupportCallback(Executor executor, RecognitionSupportCallback callback)695         private InternalSupportCallback(Executor executor, RecognitionSupportCallback callback) {
696             this.mExecutor = executor;
697             this.mCallback = callback;
698         }
699 
700         @Override
onSupportResult(RecognitionSupport recognitionSupport)701         public void onSupportResult(RecognitionSupport recognitionSupport) throws RemoteException {
702             mExecutor.execute(() -> mCallback.onSupportResult(recognitionSupport));
703         }
704 
705         @Override
onError(int errorCode)706         public void onError(int errorCode) throws RemoteException {
707             mExecutor.execute(() -> mCallback.onError(errorCode));
708         }
709     }
710 
711     private static class InternalModelDownloadListener extends IModelDownloadListener.Stub {
712         private final Executor mExecutor;
713         private final ModelDownloadListener mModelDownloadListener;
714 
InternalModelDownloadListener( Executor executor, @NonNull ModelDownloadListener modelDownloadListener)715         private InternalModelDownloadListener(
716                 Executor executor,
717                 @NonNull ModelDownloadListener modelDownloadListener) {
718             mExecutor = executor;
719             mModelDownloadListener = modelDownloadListener;
720         }
721 
722         @Override
onProgress(int completedPercent)723         public void onProgress(int completedPercent) throws RemoteException {
724             mExecutor.execute(() -> mModelDownloadListener.onProgress(completedPercent));
725         }
726 
727         @Override
onSuccess()728         public void onSuccess() throws RemoteException {
729             mExecutor.execute(() -> mModelDownloadListener.onSuccess());
730         }
731 
732         @Override
onScheduled()733         public void onScheduled() throws RemoteException {
734             mExecutor.execute(() -> mModelDownloadListener.onScheduled());
735         }
736 
737         @Override
onError(int error)738         public void onError(int error) throws RemoteException {
739             mExecutor.execute(() -> mModelDownloadListener.onError(error));
740         }
741     }
742 }
743