• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.IntDef;
22 import android.annotation.MainThread;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.TestApi;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.ResolveInfo;
31 import android.os.Binder;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.provider.Settings;
40 import android.text.TextUtils;
41 import android.util.Log;
42 import android.util.Slog;
43 
44 import com.android.internal.R;
45 
46 import java.lang.annotation.Documented;
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.List;
50 import java.util.Objects;
51 import java.util.Queue;
52 import java.util.concurrent.Executor;
53 import java.util.concurrent.LinkedBlockingQueue;
54 
55 /**
56  * This class provides access to the speech recognition service. This service allows access to the
57  * speech recognizer. Do not instantiate this class directly, instead, call
58  * {@link SpeechRecognizer#createSpeechRecognizer(Context)}, or
59  * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be
60  * invoked only from the main application thread.
61  *
62  * <p>The implementation of this API is likely to stream audio to remote servers to perform speech
63  * recognition. As such this API is not intended to be used for continuous recognition, which would
64  * consume a significant amount of battery and bandwidth.
65  *
66  * <p>Please note that the application must have {@link android.Manifest.permission#RECORD_AUDIO}
67  * permission to use this class.
68  */
69 public class SpeechRecognizer {
70     /** DEBUG value to enable verbose debug prints */
71     private static final boolean DBG = false;
72 
73     /** Log messages identifier */
74     private static final String TAG = "SpeechRecognizer";
75 
76     /**
77      * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
78      * {@link RecognitionListener#onResults(Bundle)} and
79      * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
80      * recognition results, where the first element is the most likely candidate.
81      */
82     public static final String RESULTS_RECOGNITION = "results_recognition";
83 
84     /**
85      * Key used to retrieve a float array from the {@link Bundle} passed to the
86      * {@link RecognitionListener#onResults(Bundle)} and
87      * {@link RecognitionListener#onPartialResults(Bundle)} methods. The array should be
88      * the same size as the ArrayList provided in {@link #RESULTS_RECOGNITION}, and should contain
89      * values ranging from 0.0 to 1.0, or -1 to represent an unavailable confidence score.
90      * <p>
91      * Confidence values close to 1.0 indicate high confidence (the speech recognizer is confident
92      * that the recognition result is correct), while values close to 0.0 indicate low confidence.
93      * <p>
94      * This value is optional and might not be provided.
95      */
96     public static final String CONFIDENCE_SCORES = "confidence_scores";
97 
98     /**
99      * The reason speech recognition failed.
100      *
101      * @hide
102      */
103     @Documented
104     @Retention(RetentionPolicy.SOURCE)
105     @IntDef(prefix = {"ERROR_"}, value = {
106             ERROR_NETWORK_TIMEOUT,
107             ERROR_NETWORK,
108             ERROR_AUDIO,
109             ERROR_SERVER,
110             ERROR_CLIENT,
111             ERROR_SPEECH_TIMEOUT,
112             ERROR_NO_MATCH,
113             ERROR_RECOGNIZER_BUSY,
114             ERROR_INSUFFICIENT_PERMISSIONS,
115             ERROR_TOO_MANY_REQUESTS,
116             ERROR_SERVER_DISCONNECTED,
117             ERROR_LANGUAGE_NOT_SUPPORTED,
118             ERROR_LANGUAGE_UNAVAILABLE,
119             ERROR_CANNOT_CHECK_SUPPORT,
120     })
121     public @interface RecognitionError {}
122 
123     /** Network operation timed out. */
124     public static final int ERROR_NETWORK_TIMEOUT = 1;
125 
126     /** Other network related errors. */
127     public static final int ERROR_NETWORK = 2;
128 
129     /** Audio recording error. */
130     public static final int ERROR_AUDIO = 3;
131 
132     /** Server sends error status. */
133     public static final int ERROR_SERVER = 4;
134 
135     /** Other client side errors. */
136     public static final int ERROR_CLIENT = 5;
137 
138     /** No speech input */
139     public static final int ERROR_SPEECH_TIMEOUT = 6;
140 
141     /** No recognition result matched. */
142     public static final int ERROR_NO_MATCH = 7;
143 
144     /** RecognitionService busy. */
145     public static final int ERROR_RECOGNIZER_BUSY = 8;
146 
147     /** Insufficient permissions */
148     public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
149 
150     /** Too many requests from the same client. */
151     public static final int ERROR_TOO_MANY_REQUESTS = 10;
152 
153     /** Server has been disconnected, e.g. because the app has crashed. */
154     public static final int ERROR_SERVER_DISCONNECTED = 11;
155 
156     /** Requested language is not available to be used with the current recognizer. */
157     public static final int ERROR_LANGUAGE_NOT_SUPPORTED = 12;
158 
159     /** Requested language is supported, but not available currently (e.g. not downloaded yet). */
160     public static final int ERROR_LANGUAGE_UNAVAILABLE = 13;
161 
162     /** The service does not allow to check for support. */
163     public static final int ERROR_CANNOT_CHECK_SUPPORT = 14;
164 
165     /** action codes */
166     private static final int MSG_START = 1;
167     private static final int MSG_STOP = 2;
168     private static final int MSG_CANCEL = 3;
169     private static final int MSG_CHANGE_LISTENER = 4;
170     private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
171     private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
172     private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
173 
174     /** The actual RecognitionService endpoint */
175     private IRecognitionService mService;
176 
177     /** Context with which the manager was created */
178     private final Context mContext;
179 
180     /** Component to direct service intent to */
181     private final ComponentName mServiceComponent;
182 
183     /** Whether to use on-device speech recognizer. */
184     private final boolean mOnDevice;
185 
186     private IRecognitionServiceManager mManagerService;
187 
188     /** Handler that will execute the main tasks */
189     private Handler mHandler = new Handler(Looper.getMainLooper()) {
190 
191         @Override
192         public void handleMessage(Message msg) {
193             switch (msg.what) {
194                 case MSG_START:
195                     handleStartListening((Intent) msg.obj);
196                     break;
197                 case MSG_STOP:
198                     handleStopMessage();
199                     break;
200                 case MSG_CANCEL:
201                     handleCancelMessage();
202                     break;
203                 case MSG_CHANGE_LISTENER:
204                     handleChangeListener((RecognitionListener) msg.obj);
205                     break;
206                 case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT:
207                     handleSetTemporaryComponent((ComponentName) msg.obj);
208                     break;
209                 case MSG_CHECK_RECOGNITION_SUPPORT:
210                     CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj;
211                     handleCheckRecognitionSupport(
212                             args.mIntent, args.mCallbackExecutor, args.mCallback);
213                     break;
214                 case MSG_TRIGGER_MODEL_DOWNLOAD:
215                     handleTriggerModelDownload((Intent) msg.obj);
216                     break;
217             }
218         }
219     };
220 
221     /**
222      * Temporary queue, saving the messages until the connection will be established, afterwards,
223      * only mHandler will receive the messages
224      */
225     private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>();
226 
227     /** The Listener that will receive all the callbacks */
228     private final InternalRecognitionListener mListener = new InternalRecognitionListener();
229 
230     private final IBinder mClientToken = new Binder();
231 
232     /**
233      * The right way to create a {@code SpeechRecognizer} is by using
234      * {@link #createSpeechRecognizer} static factory method
235      */
SpeechRecognizer(final Context context, final ComponentName serviceComponent)236     private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
237         mContext = context;
238         mServiceComponent = serviceComponent;
239         mOnDevice = false;
240     }
241 
242     /**
243      * The right way to create a {@code SpeechRecognizer} is by using
244      * {@link #createOnDeviceSpeechRecognizer} static factory method
245      */
SpeechRecognizer(final Context context, boolean onDevice)246     private SpeechRecognizer(final Context context, boolean onDevice) {
247         mContext = context;
248         mServiceComponent = null;
249         mOnDevice = onDevice;
250     }
251 
252     /**
253      * Checks whether a speech recognition service is available on the system. If this method
254      * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
255      * fail.
256      *
257      * @param context with which {@code SpeechRecognizer} will be created
258      * @return {@code true} if recognition is available, {@code false} otherwise
259      */
isRecognitionAvailable(@onNull final Context context)260     public static boolean isRecognitionAvailable(@NonNull final Context context) {
261         // TODO(b/176578753): make sure this works well with system speech recognizers.
262         final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
263                 new Intent(RecognitionService.SERVICE_INTERFACE), 0);
264         return list != null && list.size() != 0;
265     }
266 
267     /**
268      * Checks whether an on-device speech recognition service is available on the system. If this
269      * method returns {@code false},
270      * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)} will
271      * fail.
272      *
273      * @param context with which on-device {@code SpeechRecognizer} will be created
274      * @return {@code true} if on-device recognition is available, {@code false} otherwise
275      */
isOnDeviceRecognitionAvailable(@onNull final Context context)276     public static boolean isOnDeviceRecognitionAvailable(@NonNull final Context context) {
277         ComponentName componentName =
278                 ComponentName.unflattenFromString(
279                         context.getString(R.string.config_defaultOnDeviceSpeechRecognitionService));
280         return componentName != null;
281     }
282 
283     /**
284      * Factory method to create a new {@code SpeechRecognizer}. Please note that
285      * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
286      * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
287      * received.
288      *
289      * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition
290      * service requires <queries> element to be added to the manifest file:
291      * <pre>{@code
292      * <queries>
293      *   <intent>
294      *     <action
295      *        android:name="android.speech.RecognitionService" />
296      *   </intent>
297      * </queries>
298      * }</pre>
299      *
300      * @param context in which to create {@code SpeechRecognizer}
301      * @return a new {@code SpeechRecognizer}
302      */
303     @MainThread
createSpeechRecognizer(final Context context)304     public static SpeechRecognizer createSpeechRecognizer(final Context context) {
305         return createSpeechRecognizer(context, null);
306     }
307 
308     /**
309      * Factory method to create a new {@code SpeechRecognizer}. Please note that
310      * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
311      * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
312      * received.
313      * Use this version of the method to specify a specific service to direct this
314      * {@link SpeechRecognizer} to.
315      *
316      * <p><strong>Important</strong>: before calling this method, please check via
317      * {@link android.content.pm.PackageManager#queryIntentServices(Intent, int)} that {@code
318      * serviceComponent} actually exists and provides
319      * {@link RecognitionService#SERVICE_INTERFACE}. Normally you would not use this; call
320      * {@link #createSpeechRecognizer(Context)} to use the system default recognition
321      * service instead or {@link #createOnDeviceSpeechRecognizer(Context)} to use on-device
322      * recognition.</p>
323      *
324      * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition
325      * service requires <queries> element to be added to the manifest file:
326      * <pre>{@code
327      * <queries>
328      *   <intent>
329      *     <action
330      *        android:name="android.speech.RecognitionService" />
331      *   </intent>
332      * </queries>
333      * }</pre>
334      *
335      * @param context in which to create {@code SpeechRecognizer}
336      * @param serviceComponent the {@link ComponentName} of a specific service to direct this
337      *        {@code SpeechRecognizer} to
338      * @return a new {@code SpeechRecognizer}
339      */
340     @MainThread
createSpeechRecognizer(final Context context, final ComponentName serviceComponent)341     public static SpeechRecognizer createSpeechRecognizer(final Context context,
342             final ComponentName serviceComponent) {
343         if (context == null) {
344             throw new IllegalArgumentException("Context cannot be null");
345         }
346         checkIsCalledFromMainThread();
347         return new SpeechRecognizer(context, serviceComponent);
348     }
349 
350     /**
351      * Factory method to create a new {@code SpeechRecognizer}.
352      *
353      * <p>Please note that {@link #setRecognitionListener(RecognitionListener)} should be called
354      * before dispatching any command to the created {@code SpeechRecognizer}, otherwise no
355      * notifications will be received.
356      *
357      * @param context in which to create {@code SpeechRecognizer}
358      * @return a new on-device {@code SpeechRecognizer}.
359      * @throws UnsupportedOperationException iff {@link #isOnDeviceRecognitionAvailable(Context)}
360      *                                       is false
361      */
362     @NonNull
363     @MainThread
createOnDeviceSpeechRecognizer(@onNull final Context context)364     public static SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull final Context context) {
365         if (!isOnDeviceRecognitionAvailable(context)) {
366             throw new UnsupportedOperationException("On-device recognition is not available");
367         }
368         return lenientlyCreateOnDeviceSpeechRecognizer(context);
369     }
370 
371     /**
372      * Helper method to create on-device SpeechRecognizer in tests even when the device does not
373      * support on-device speech recognition.
374      *
375      * @hide
376      */
377     @TestApi
378     @NonNull
379     @MainThread
createOnDeviceTestingSpeechRecognizer( @onNull final Context context)380     public static SpeechRecognizer createOnDeviceTestingSpeechRecognizer(
381             @NonNull final Context context) {
382         return lenientlyCreateOnDeviceSpeechRecognizer(context);
383     }
384 
385     @NonNull
386     @MainThread
lenientlyCreateOnDeviceSpeechRecognizer( @onNull final Context context)387     private static SpeechRecognizer lenientlyCreateOnDeviceSpeechRecognizer(
388             @NonNull final Context context) {
389         if (context == null) {
390             throw new IllegalArgumentException("Context cannot be null");
391         }
392         checkIsCalledFromMainThread();
393         return new SpeechRecognizer(context, /* onDevice */ true);
394     }
395 
396     /**
397      * Sets the listener that will receive all the callbacks. The previous unfinished commands will
398      * be executed with the old listener, while any following command will be executed with the new
399      * listener.
400      *
401      * @param listener listener that will receive all the callbacks from the created
402      *        {@link SpeechRecognizer}, this must not be null.
403      */
404     @MainThread
setRecognitionListener(RecognitionListener listener)405     public void setRecognitionListener(RecognitionListener listener) {
406         checkIsCalledFromMainThread();
407         putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
408     }
409 
410     /**
411      * Starts listening for speech. Please note that
412      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
413      * no notifications will be received.
414      *
415      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
416      *        may also contain optional extras, see {@link RecognizerIntent}. If these values are
417      *        not set explicitly, default values will be used by the recognizer.
418      */
419     @MainThread
startListening(final Intent recognizerIntent)420     public void startListening(final Intent recognizerIntent) {
421         if (recognizerIntent == null) {
422             throw new IllegalArgumentException("intent must not be null");
423         }
424         checkIsCalledFromMainThread();
425 
426         if (DBG) {
427             Slog.i(TAG, "#startListening called");
428             if (mService == null) {
429                 Slog.i(TAG, "Connection is not established yet");
430             }
431         }
432 
433         if (mService == null) {
434             // First time connection: first establish a connection, then dispatch #startListening.
435             connectToSystemService();
436         }
437         putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
438     }
439 
440     /**
441      * Stops listening for speech. Speech captured so far will be recognized as if the user had
442      * stopped speaking at this point.
443      *
444      * <p>Note that in the default case, this does not need to be called, as the speech endpointer
445      * will automatically stop the recognizer listening when it determines speech has completed.
446      * However, you can manipulate endpointer parameters directly using the intent extras defined in
447      * {@link RecognizerIntent}, in which case you may sometimes want to manually call this method
448      * to stop listening sooner.
449      *
450      * <p>Upon invocation clients must wait until {@link RecognitionListener#onResults} or
451      * {@link RecognitionListener#onError} are invoked before calling
452      * {@link SpeechRecognizer#startListening} again. Otherwise such an attempt would be rejected by
453      * recognition service.
454      *
455      * <p>Please note that
456      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
457      * no notifications will be received.
458      */
459     @MainThread
stopListening()460     public void stopListening() {
461         checkIsCalledFromMainThread();
462 
463         if (DBG) {
464             Slog.i(TAG, "#stopListening called");
465             if (mService == null) {
466                 Slog.i(TAG, "Connection is not established yet");
467             }
468         }
469 
470         putMessage(Message.obtain(mHandler, MSG_STOP));
471     }
472 
473     /**
474      * Cancels the speech recognition. Please note that
475      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
476      * no notifications will be received.
477      */
478     @MainThread
cancel()479     public void cancel() {
480         checkIsCalledFromMainThread();
481         putMessage(Message.obtain(mHandler, MSG_CANCEL));
482     }
483 
484     /**
485      * Checks whether {@code recognizerIntent} is supported by
486      * {@link SpeechRecognizer#startListening(Intent)}.
487      *
488      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
489      *        may also contain optional extras. See {@link RecognizerIntent} for the list of
490      *        supported extras, any unlisted extra might be ignored.
491      * @param supportListener the listener on which to receive the support query results.
492      */
checkRecognitionSupport( @onNull Intent recognizerIntent, @NonNull @CallbackExecutor Executor executor, @NonNull RecognitionSupportCallback supportListener)493     public void checkRecognitionSupport(
494             @NonNull Intent recognizerIntent,
495             @NonNull @CallbackExecutor Executor executor,
496             @NonNull RecognitionSupportCallback supportListener) {
497         Objects.requireNonNull(recognizerIntent, "intent must not be null");
498         Objects.requireNonNull(supportListener, "listener must not be null");
499 
500         if (DBG) {
501             Slog.i(TAG, "#checkRecognitionSupport called");
502             if (mService == null) {
503                 Slog.i(TAG, "Connection is not established yet");
504             }
505         }
506 
507         if (mService == null) {
508             // First time connection: first establish a connection, then dispatch.
509             connectToSystemService();
510         }
511         putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT,
512                 new CheckRecognitionSupportArgs(recognizerIntent, executor, supportListener)));
513     }
514 
515     /**
516      * Attempts to download the support for the given {@code recognizerIntent}. This might trigger
517      * user interaction to approve the download. Callers can verify the status of the request via
518      * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
519      *
520      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
521      *        may also contain optional extras, see {@link RecognizerIntent}.
522      */
triggerModelDownload(@onNull Intent recognizerIntent)523     public void triggerModelDownload(@NonNull Intent recognizerIntent) {
524         Objects.requireNonNull(recognizerIntent, "intent must not be null");
525         if (DBG) {
526             Slog.i(TAG, "#triggerModelDownload called");
527             if (mService == null) {
528                 Slog.i(TAG, "Connection is not established yet");
529             }
530         }
531         if (mService == null) {
532             // First time connection: first establish a connection, then dispatch.
533             connectToSystemService();
534         }
535         putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
536     }
537 
538     /**
539      * Sets a temporary component to power on-device speech recognizer.
540      *
541      * <p>This is only expected to be called in tests, system would reject calls from client apps.
542      *
543      * @param componentName name of the component to set temporary replace speech recognizer. {@code
544      *        null} value resets the recognizer to default.
545      *
546      * @hide
547      */
548     @TestApi
549     @RequiresPermission(Manifest.permission.MANAGE_SPEECH_RECOGNITION)
setTemporaryOnDeviceRecognizer(@ullable ComponentName componentName)550     public void setTemporaryOnDeviceRecognizer(@Nullable ComponentName componentName) {
551         mHandler.sendMessage(
552                 Message.obtain(mHandler, MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT, componentName));
553     }
554 
checkIsCalledFromMainThread()555     private static void checkIsCalledFromMainThread() {
556         if (Looper.myLooper() != Looper.getMainLooper()) {
557             throw new RuntimeException(
558                     "SpeechRecognizer should be used only from the application's main thread");
559         }
560     }
561 
putMessage(Message msg)562     private void putMessage(Message msg) {
563         if (mService == null) {
564             mPendingTasks.offer(msg);
565         } else {
566             mHandler.sendMessage(msg);
567         }
568     }
569 
570     /** sends the actual message to the service */
handleStartListening(Intent recognizerIntent)571     private void handleStartListening(Intent recognizerIntent) {
572         if (!checkOpenConnection()) {
573             return;
574         }
575         try {
576             mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource());
577             if (DBG) Log.d(TAG, "service start listening command succeeded");
578         } catch (final RemoteException e) {
579             Log.e(TAG, "startListening() failed", e);
580             mListener.onError(ERROR_CLIENT);
581         }
582     }
583 
584     /** sends the actual message to the service */
handleStopMessage()585     private void handleStopMessage() {
586         if (!checkOpenConnection()) {
587             return;
588         }
589         try {
590             mService.stopListening(mListener);
591             if (DBG) Log.d(TAG, "service stop listening command succeeded");
592         } catch (final RemoteException e) {
593             Log.e(TAG, "stopListening() failed", e);
594             mListener.onError(ERROR_CLIENT);
595         }
596     }
597 
598     /** sends the actual message to the service */
handleCancelMessage()599     private void handleCancelMessage() {
600         if (!checkOpenConnection()) {
601             return;
602         }
603         try {
604             mService.cancel(mListener, /*isShutdown*/ false);
605             if (DBG) Log.d(TAG, "service cancel command succeeded");
606         } catch (final RemoteException e) {
607             Log.e(TAG, "cancel() failed", e);
608             mListener.onError(ERROR_CLIENT);
609         }
610     }
611 
handleSetTemporaryComponent(ComponentName componentName)612     private void handleSetTemporaryComponent(ComponentName componentName) {
613         if (DBG) {
614             Log.d(TAG, "handleSetTemporaryComponent, componentName=" + componentName);
615         }
616 
617         if (!maybeInitializeManagerService()) {
618             return;
619         }
620 
621         try {
622             mManagerService.setTemporaryComponent(componentName);
623         } catch (final RemoteException e) {
624             e.rethrowFromSystemServer();
625         }
626     }
627 
handleCheckRecognitionSupport( Intent recognizerIntent, Executor callbackExecutor, RecognitionSupportCallback recognitionSupportCallback)628     private void handleCheckRecognitionSupport(
629             Intent recognizerIntent,
630             Executor callbackExecutor,
631             RecognitionSupportCallback recognitionSupportCallback) {
632         if (!maybeInitializeManagerService()) {
633             return;
634         }
635         try {
636             mService.checkRecognitionSupport(
637                     recognizerIntent,
638                     new InternalSupportCallback(callbackExecutor, recognitionSupportCallback));
639             if (DBG) Log.d(TAG, "service support command succeeded");
640         } catch (final RemoteException e) {
641             Log.e(TAG, "checkRecognitionSupport() failed", e);
642             callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT));
643         }
644     }
645 
handleTriggerModelDownload(Intent recognizerIntent)646     private void handleTriggerModelDownload(Intent recognizerIntent) {
647         if (!maybeInitializeManagerService()) {
648             return;
649         }
650         try {
651             mService.triggerModelDownload(recognizerIntent);
652         } catch (final RemoteException e) {
653             Log.e(TAG, "downloadModel() failed", e);
654             mListener.onError(ERROR_CLIENT);
655         }
656     }
657 
checkOpenConnection()658     private boolean checkOpenConnection() {
659         if (mService != null) {
660             return true;
661         }
662         mListener.onError(ERROR_CLIENT);
663         Log.e(TAG, "not connected to the recognition service");
664         return false;
665     }
666 
667     /** changes the listener */
handleChangeListener(RecognitionListener listener)668     private void handleChangeListener(RecognitionListener listener) {
669         if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
670         mListener.mInternalListener = listener;
671     }
672 
673     /** Destroys the {@code SpeechRecognizer} object. */
destroy()674     public void destroy() {
675         if (mService != null) {
676             try {
677                 mService.cancel(mListener, /*isShutdown*/ true);
678             } catch (final RemoteException e) {
679                 // Not important
680             }
681         }
682 
683         mService = null;
684         mPendingTasks.clear();
685         mListener.mInternalListener = null;
686     }
687 
688     /** Establishes a connection to system server proxy and initializes the session. */
connectToSystemService()689     private void connectToSystemService() {
690         if (!maybeInitializeManagerService()) {
691             return;
692         }
693 
694         ComponentName componentName = getSpeechRecognizerComponentName();
695 
696         if (!mOnDevice && componentName == null) {
697             mListener.onError(ERROR_CLIENT);
698             return;
699         }
700 
701         try {
702             mManagerService.createSession(
703                     componentName,
704                     mClientToken,
705                     mOnDevice,
706                     new IRecognitionServiceManagerCallback.Stub(){
707                         @Override
708                         public void onSuccess(IRecognitionService service) throws RemoteException {
709                             if (DBG) {
710                                 Log.i(TAG, "Connected to speech recognition service");
711                             }
712                             mService = service;
713                             while (!mPendingTasks.isEmpty()) {
714                                 mHandler.sendMessage(mPendingTasks.poll());
715                             }
716                         }
717 
718                         @Override
719                         public void onError(int errorCode) throws RemoteException {
720                             Log.e(TAG, "Bind to system recognition service failed with error "
721                                     + errorCode);
722                             mListener.onError(errorCode);
723                         }
724                     });
725         } catch (RemoteException e) {
726             e.rethrowFromSystemServer();
727         }
728     }
729 
maybeInitializeManagerService()730     private synchronized boolean maybeInitializeManagerService() {
731         if (DBG) {
732             Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService);
733         }
734         if (mManagerService != null) {
735             return true;
736         }
737 
738         mManagerService = IRecognitionServiceManager.Stub.asInterface(
739                 ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
740 
741         if (DBG) {
742             Log.i(TAG, "#maybeInitializeManagerService instantiated =" + mManagerService);
743         }
744         if (mManagerService == null) {
745             if (mListener != null) {
746                 mListener.onError(ERROR_CLIENT);
747             }
748             return false;
749         }
750         return true;
751     }
752 
753     /**
754      * Returns the component name to be used for establishing a connection, based on the parameters
755      * used during initialization.
756      *
757      * <p>Note the 3 different scenarios:
758      * <ol>
759      *     <li>On-device speech recognizer which is determined by the manufacturer and not
760      *     changeable by the user
761      *     <li>Default user-selected speech recognizer as specified by
762      *     {@code Settings.Secure.VOICE_RECOGNITION_SERVICE}
763      *     <li>Custom speech recognizer supplied by the client.
764      */
getSpeechRecognizerComponentName()765     private ComponentName getSpeechRecognizerComponentName() {
766         if (mOnDevice) {
767             return null;
768         }
769 
770         if (mServiceComponent != null) {
771             return mServiceComponent;
772         }
773 
774         String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
775                 Settings.Secure.VOICE_RECOGNITION_SERVICE);
776 
777         if (TextUtils.isEmpty(serviceComponent)) {
778             Log.e(TAG, "no selected voice recognition service");
779             mListener.onError(ERROR_CLIENT);
780             return null;
781         }
782 
783         return ComponentName.unflattenFromString(serviceComponent);
784     }
785 
786     private static class CheckRecognitionSupportArgs {
787         final Intent mIntent;
788         final Executor mCallbackExecutor;
789         final RecognitionSupportCallback mCallback;
790 
CheckRecognitionSupportArgs( Intent intent, Executor callbackExecutor, RecognitionSupportCallback callback)791         private CheckRecognitionSupportArgs(
792                 Intent intent,
793                 Executor callbackExecutor,
794                 RecognitionSupportCallback callback) {
795             mIntent = intent;
796             mCallbackExecutor = callbackExecutor;
797             mCallback = callback;
798         }
799     }
800 
801     /**
802      * Internal wrapper of IRecognitionListener which will propagate the results to
803      * RecognitionListener
804      */
805     private static class InternalRecognitionListener extends IRecognitionListener.Stub {
806         private RecognitionListener mInternalListener;
807 
808         private static final int MSG_BEGINNING_OF_SPEECH = 1;
809         private static final int MSG_BUFFER_RECEIVED = 2;
810         private static final int MSG_END_OF_SPEECH = 3;
811         private static final int MSG_ERROR = 4;
812         private static final int MSG_READY_FOR_SPEECH = 5;
813         private static final int MSG_RESULTS = 6;
814         private static final int MSG_PARTIAL_RESULTS = 7;
815         private static final int MSG_RMS_CHANGED = 8;
816         private static final int MSG_ON_EVENT = 9;
817         private static final int MSG_SEGMENT_RESULTS = 10;
818         private static final int MSG_SEGMENT_END_SESSION = 11;
819 
820         private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) {
821             @Override
822             public void handleMessage(Message msg) {
823                 if (mInternalListener == null) {
824                     return;
825                 }
826                 switch (msg.what) {
827                     case MSG_BEGINNING_OF_SPEECH:
828                         mInternalListener.onBeginningOfSpeech();
829                         break;
830                     case MSG_BUFFER_RECEIVED:
831                         mInternalListener.onBufferReceived((byte[]) msg.obj);
832                         break;
833                     case MSG_END_OF_SPEECH:
834                         mInternalListener.onEndOfSpeech();
835                         break;
836                     case MSG_ERROR:
837                         mInternalListener.onError((Integer) msg.obj);
838                         break;
839                     case MSG_READY_FOR_SPEECH:
840                         mInternalListener.onReadyForSpeech((Bundle) msg.obj);
841                         break;
842                     case MSG_RESULTS:
843                         mInternalListener.onResults((Bundle) msg.obj);
844                         break;
845                     case MSG_PARTIAL_RESULTS:
846                         mInternalListener.onPartialResults((Bundle) msg.obj);
847                         break;
848                     case MSG_RMS_CHANGED:
849                         mInternalListener.onRmsChanged((Float) msg.obj);
850                         break;
851                     case MSG_ON_EVENT:
852                         mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
853                         break;
854                     case MSG_SEGMENT_RESULTS:
855                         mInternalListener.onSegmentResults((Bundle) msg.obj);
856                         break;
857                     case MSG_SEGMENT_END_SESSION:
858                         mInternalListener.onEndOfSegmentedSession();
859                         break;
860                 }
861             }
862         };
863 
onBeginningOfSpeech()864         public void onBeginningOfSpeech() {
865             Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
866         }
867 
onBufferReceived(final byte[] buffer)868         public void onBufferReceived(final byte[] buffer) {
869             Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
870         }
871 
onEndOfSpeech()872         public void onEndOfSpeech() {
873             Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
874         }
875 
onError(final int error)876         public void onError(final int error) {
877             Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
878         }
879 
onReadyForSpeech(final Bundle noiseParams)880         public void onReadyForSpeech(final Bundle noiseParams) {
881             Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
882         }
883 
onResults(final Bundle results)884         public void onResults(final Bundle results) {
885             Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
886         }
887 
onPartialResults(final Bundle results)888         public void onPartialResults(final Bundle results) {
889             Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
890         }
891 
onRmsChanged(final float rmsdB)892         public void onRmsChanged(final float rmsdB) {
893             Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
894         }
895 
onSegmentResults(final Bundle bundle)896         public void onSegmentResults(final Bundle bundle) {
897             Message.obtain(mInternalHandler, MSG_SEGMENT_RESULTS, bundle).sendToTarget();
898         }
899 
onEndOfSegmentedSession()900         public void onEndOfSegmentedSession() {
901             Message.obtain(mInternalHandler, MSG_SEGMENT_END_SESSION).sendToTarget();
902         }
903 
onEvent(final int eventType, final Bundle params)904         public void onEvent(final int eventType, final Bundle params) {
905             Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
906                     .sendToTarget();
907         }
908     }
909 
910     private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub {
911         private final Executor mExecutor;
912         private final RecognitionSupportCallback mCallback;
913 
InternalSupportCallback(Executor executor, RecognitionSupportCallback callback)914         private InternalSupportCallback(Executor executor, RecognitionSupportCallback callback) {
915             this.mExecutor = executor;
916             this.mCallback = callback;
917         }
918 
919         @Override
onSupportResult(RecognitionSupport recognitionSupport)920         public void onSupportResult(RecognitionSupport recognitionSupport) throws RemoteException {
921             mExecutor.execute(() -> mCallback.onSupportResult(recognitionSupport));
922         }
923 
924         @Override
onError(int errorCode)925         public void onError(int errorCode) throws RemoteException {
926             mExecutor.execute(() -> mCallback.onError(errorCode));
927         }
928     }
929 }
930