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.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.content.pm.ResolveInfo;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.RemoteException;
30 import android.provider.Settings;
31 import android.text.TextUtils;
32 import android.util.Log;
33 
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Queue;
37 
38 /**
39  * This class provides access to the speech recognition service. This service allows access to the
40  * speech recognizer. Do not instantiate this class directly, instead, call
41  * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be
42  * invoked only from the main application thread.
43  *
44  * <p>The implementation of this API is likely to stream audio to remote servers to perform speech
45  * recognition. As such this API is not intended to be used for continuous recognition, which would
46  * consume a significant amount of battery and bandwidth.
47  *
48  * <p>Please note that the application must have {@link android.Manifest.permission#RECORD_AUDIO}
49  * permission to use this class.
50  */
51 public class SpeechRecognizer {
52     /** DEBUG value to enable verbose debug prints */
53     private final static boolean DBG = false;
54 
55     /** Log messages identifier */
56     private static final String TAG = "SpeechRecognizer";
57 
58     /**
59      * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
60      * {@link RecognitionListener#onResults(Bundle)} and
61      * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
62      * recognition results, where the first element is the most likely candidate.
63      */
64     public static final String RESULTS_RECOGNITION = "results_recognition";
65 
66     /**
67      * Key used to retrieve a float array from the {@link Bundle} passed to the
68      * {@link RecognitionListener#onResults(Bundle)} and
69      * {@link RecognitionListener#onPartialResults(Bundle)} methods. The array should be
70      * the same size as the ArrayList provided in {@link #RESULTS_RECOGNITION}, and should contain
71      * values ranging from 0.0 to 1.0, or -1 to represent an unavailable confidence score.
72      * <p>
73      * Confidence values close to 1.0 indicate high confidence (the speech recognizer is confident
74      * that the recognition result is correct), while values close to 0.0 indicate low confidence.
75      * <p>
76      * This value is optional and might not be provided.
77      */
78     public static final String CONFIDENCE_SCORES = "confidence_scores";
79 
80     /** Network operation timed out. */
81     public static final int ERROR_NETWORK_TIMEOUT = 1;
82 
83     /** Other network related errors. */
84     public static final int ERROR_NETWORK = 2;
85 
86     /** Audio recording error. */
87     public static final int ERROR_AUDIO = 3;
88 
89     /** Server sends error status. */
90     public static final int ERROR_SERVER = 4;
91 
92     /** Other client side errors. */
93     public static final int ERROR_CLIENT = 5;
94 
95     /** No speech input */
96     public static final int ERROR_SPEECH_TIMEOUT = 6;
97 
98     /** No recognition result matched. */
99     public static final int ERROR_NO_MATCH = 7;
100 
101     /** RecognitionService busy. */
102     public static final int ERROR_RECOGNIZER_BUSY = 8;
103 
104     /** Insufficient permissions */
105     public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
106 
107     /** action codes */
108     private final static int MSG_START = 1;
109     private final static int MSG_STOP = 2;
110     private final static int MSG_CANCEL = 3;
111     private final static int MSG_CHANGE_LISTENER = 4;
112 
113     /** The actual RecognitionService endpoint */
114     private IRecognitionService mService;
115 
116     /** The connection to the actual service */
117     private Connection mConnection;
118 
119     /** Context with which the manager was created */
120     private final Context mContext;
121 
122     /** Component to direct service intent to */
123     private final ComponentName mServiceComponent;
124 
125     /** Handler that will execute the main tasks */
126     private Handler mHandler = new Handler() {
127         @Override
128         public void handleMessage(Message msg) {
129             switch (msg.what) {
130                 case MSG_START:
131                     handleStartListening((Intent) msg.obj);
132                     break;
133                 case MSG_STOP:
134                     handleStopMessage();
135                     break;
136                 case MSG_CANCEL:
137                     handleCancelMessage();
138                     break;
139                 case MSG_CHANGE_LISTENER:
140                     handleChangeListener((RecognitionListener) msg.obj);
141                     break;
142             }
143         }
144     };
145 
146     /**
147      * Temporary queue, saving the messages until the connection will be established, afterwards,
148      * only mHandler will receive the messages
149      */
150     private final Queue<Message> mPendingTasks = new LinkedList<Message>();
151 
152     /** The Listener that will receive all the callbacks */
153     private final InternalListener mListener = new InternalListener();
154 
155     /**
156      * The right way to create a {@code SpeechRecognizer} is by using
157      * {@link #createSpeechRecognizer} static factory method
158      */
SpeechRecognizer(final Context context, final ComponentName serviceComponent)159     private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
160         mContext = context;
161         mServiceComponent = serviceComponent;
162     }
163 
164     /**
165      * Basic ServiceConnection that records the mService variable. Additionally, on creation it
166      * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
167      */
168     private class Connection implements ServiceConnection {
169 
onServiceConnected(final ComponentName name, final IBinder service)170         public void onServiceConnected(final ComponentName name, final IBinder service) {
171             // always done on the application main thread, so no need to send message to mHandler
172             mService = IRecognitionService.Stub.asInterface(service);
173             if (DBG) Log.d(TAG, "onServiceConnected - Success");
174             while (!mPendingTasks.isEmpty()) {
175                 mHandler.sendMessage(mPendingTasks.poll());
176             }
177         }
178 
onServiceDisconnected(final ComponentName name)179         public void onServiceDisconnected(final ComponentName name) {
180             // always done on the application main thread, so no need to send message to mHandler
181             mService = null;
182             mConnection = null;
183             mPendingTasks.clear();
184             if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
185         }
186     }
187 
188     /**
189      * Checks whether a speech recognition service is available on the system. If this method
190      * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
191      * fail.
192      *
193      * @param context with which {@code SpeechRecognizer} will be created
194      * @return {@code true} if recognition is available, {@code false} otherwise
195      */
isRecognitionAvailable(final Context context)196     public static boolean isRecognitionAvailable(final Context context) {
197         final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
198                 new Intent(RecognitionService.SERVICE_INTERFACE), 0);
199         return list != null && list.size() != 0;
200     }
201 
202     /**
203      * Factory method to create a new {@code SpeechRecognizer}. Please note that
204      * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
205      * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
206      * received.
207      *
208      * @param context in which to create {@code SpeechRecognizer}
209      * @return a new {@code SpeechRecognizer}
210      */
createSpeechRecognizer(final Context context)211     public static SpeechRecognizer createSpeechRecognizer(final Context context) {
212         return createSpeechRecognizer(context, null);
213     }
214 
215     /**
216      * Factory method to create a new {@code SpeechRecognizer}. Please note that
217      * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
218      * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
219      * received.
220      *
221      * Use this version of the method to specify a specific service to direct this
222      * {@link SpeechRecognizer} to. Normally you would not use this; use
223      * {@link #createSpeechRecognizer(Context)} instead to use the system default recognition
224      * service.
225      *
226      * @param context in which to create {@code SpeechRecognizer}
227      * @param serviceComponent the {@link ComponentName} of a specific service to direct this
228      *        {@code SpeechRecognizer} to
229      * @return a new {@code SpeechRecognizer}
230      */
createSpeechRecognizer(final Context context, final ComponentName serviceComponent)231     public static SpeechRecognizer createSpeechRecognizer(final Context context,
232             final ComponentName serviceComponent) {
233         if (context == null) {
234             throw new IllegalArgumentException("Context cannot be null)");
235         }
236         checkIsCalledFromMainThread();
237         return new SpeechRecognizer(context, serviceComponent);
238     }
239 
240     /**
241      * Sets the listener that will receive all the callbacks. The previous unfinished commands will
242      * be executed with the old listener, while any following command will be executed with the new
243      * listener.
244      *
245      * @param listener listener that will receive all the callbacks from the created
246      *        {@link SpeechRecognizer}, this must not be null.
247      */
setRecognitionListener(RecognitionListener listener)248     public void setRecognitionListener(RecognitionListener listener) {
249         checkIsCalledFromMainThread();
250         putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
251     }
252 
253     /**
254      * Starts listening for speech. Please note that
255      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
256      * no notifications will be received.
257      *
258      * @param recognizerIntent contains parameters for the recognition to be performed. The intent
259      *        may also contain optional extras, see {@link RecognizerIntent}. If these values are
260      *        not set explicitly, default values will be used by the recognizer.
261      */
startListening(final Intent recognizerIntent)262     public void startListening(final Intent recognizerIntent) {
263         if (recognizerIntent == null) {
264             throw new IllegalArgumentException("intent must not be null");
265         }
266         checkIsCalledFromMainThread();
267         if (mConnection == null) { // first time connection
268             mConnection = new Connection();
269 
270             Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
271 
272             if (mServiceComponent == null) {
273                 String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
274                         Settings.Secure.VOICE_RECOGNITION_SERVICE);
275 
276                 if (TextUtils.isEmpty(serviceComponent)) {
277                     Log.e(TAG, "no selected voice recognition service");
278                     mListener.onError(ERROR_CLIENT);
279                     return;
280                 }
281 
282                 serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));
283             } else {
284                 serviceIntent.setComponent(mServiceComponent);
285             }
286 
287             if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
288                 Log.e(TAG, "bind to recognition service failed");
289                 mConnection = null;
290                 mService = null;
291                 mListener.onError(ERROR_CLIENT);
292                 return;
293             }
294         }
295         putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
296     }
297 
298     /**
299      * Stops listening for speech. Speech captured so far will be recognized as if the user had
300      * stopped speaking at this point. Note that in the default case, this does not need to be
301      * called, as the speech endpointer will automatically stop the recognizer listening when it
302      * determines speech has completed. However, you can manipulate endpointer parameters directly
303      * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
304      * want to manually call this method to stop listening sooner. Please note that
305      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
306      * no notifications will be received.
307      */
stopListening()308     public void stopListening() {
309         checkIsCalledFromMainThread();
310         putMessage(Message.obtain(mHandler, MSG_STOP));
311     }
312 
313     /**
314      * Cancels the speech recognition. Please note that
315      * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
316      * no notifications will be received.
317      */
cancel()318     public void cancel() {
319         checkIsCalledFromMainThread();
320         putMessage(Message.obtain(mHandler, MSG_CANCEL));
321     }
322 
checkIsCalledFromMainThread()323     private static void checkIsCalledFromMainThread() {
324         if (Looper.myLooper() != Looper.getMainLooper()) {
325             throw new RuntimeException(
326                     "SpeechRecognizer should be used only from the application's main thread");
327         }
328     }
329 
putMessage(Message msg)330     private void putMessage(Message msg) {
331         if (mService == null) {
332             mPendingTasks.offer(msg);
333         } else {
334             mHandler.sendMessage(msg);
335         }
336     }
337 
338     /** sends the actual message to the service */
handleStartListening(Intent recognizerIntent)339     private void handleStartListening(Intent recognizerIntent) {
340         if (!checkOpenConnection()) {
341             return;
342         }
343         try {
344             mService.startListening(recognizerIntent, mListener);
345             if (DBG) Log.d(TAG, "service start listening command succeded");
346         } catch (final RemoteException e) {
347             Log.e(TAG, "startListening() failed", e);
348             mListener.onError(ERROR_CLIENT);
349         }
350     }
351 
352     /** sends the actual message to the service */
handleStopMessage()353     private void handleStopMessage() {
354         if (!checkOpenConnection()) {
355             return;
356         }
357         try {
358             mService.stopListening(mListener);
359             if (DBG) Log.d(TAG, "service stop listening command succeded");
360         } catch (final RemoteException e) {
361             Log.e(TAG, "stopListening() failed", e);
362             mListener.onError(ERROR_CLIENT);
363         }
364     }
365 
366     /** sends the actual message to the service */
handleCancelMessage()367     private void handleCancelMessage() {
368         if (!checkOpenConnection()) {
369             return;
370         }
371         try {
372             mService.cancel(mListener);
373             if (DBG) Log.d(TAG, "service cancel command succeded");
374         } catch (final RemoteException e) {
375             Log.e(TAG, "cancel() failed", e);
376             mListener.onError(ERROR_CLIENT);
377         }
378     }
379 
checkOpenConnection()380     private boolean checkOpenConnection() {
381         if (mService != null) {
382             return true;
383         }
384         mListener.onError(ERROR_CLIENT);
385         Log.e(TAG, "not connected to the recognition service");
386         return false;
387     }
388 
389     /** changes the listener */
handleChangeListener(RecognitionListener listener)390     private void handleChangeListener(RecognitionListener listener) {
391         if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
392         mListener.mInternalListener = listener;
393     }
394 
395     /**
396      * Destroys the {@code SpeechRecognizer} object.
397      */
destroy()398     public void destroy() {
399         if (mService != null) {
400             try {
401                 mService.cancel(mListener);
402             } catch (final RemoteException e) {
403                 // Not important
404             }
405         }
406 
407         if (mConnection != null) {
408             mContext.unbindService(mConnection);
409         }
410         mPendingTasks.clear();
411         mService = null;
412         mConnection = null;
413         mListener.mInternalListener = null;
414     }
415 
416     /**
417      * Internal wrapper of IRecognitionListener which will propagate the results to
418      * RecognitionListener
419      */
420     private static class InternalListener extends IRecognitionListener.Stub {
421         private RecognitionListener mInternalListener;
422 
423         private final static int MSG_BEGINNING_OF_SPEECH = 1;
424         private final static int MSG_BUFFER_RECEIVED = 2;
425         private final static int MSG_END_OF_SPEECH = 3;
426         private final static int MSG_ERROR = 4;
427         private final static int MSG_READY_FOR_SPEECH = 5;
428         private final static int MSG_RESULTS = 6;
429         private final static int MSG_PARTIAL_RESULTS = 7;
430         private final static int MSG_RMS_CHANGED = 8;
431         private final static int MSG_ON_EVENT = 9;
432 
433         private final Handler mInternalHandler = new Handler() {
434             @Override
435             public void handleMessage(Message msg) {
436                 if (mInternalListener == null) {
437                     return;
438                 }
439                 switch (msg.what) {
440                     case MSG_BEGINNING_OF_SPEECH:
441                         mInternalListener.onBeginningOfSpeech();
442                         break;
443                     case MSG_BUFFER_RECEIVED:
444                         mInternalListener.onBufferReceived((byte[]) msg.obj);
445                         break;
446                     case MSG_END_OF_SPEECH:
447                         mInternalListener.onEndOfSpeech();
448                         break;
449                     case MSG_ERROR:
450                         mInternalListener.onError((Integer) msg.obj);
451                         break;
452                     case MSG_READY_FOR_SPEECH:
453                         mInternalListener.onReadyForSpeech((Bundle) msg.obj);
454                         break;
455                     case MSG_RESULTS:
456                         mInternalListener.onResults((Bundle) msg.obj);
457                         break;
458                     case MSG_PARTIAL_RESULTS:
459                         mInternalListener.onPartialResults((Bundle) msg.obj);
460                         break;
461                     case MSG_RMS_CHANGED:
462                         mInternalListener.onRmsChanged((Float) msg.obj);
463                         break;
464                     case MSG_ON_EVENT:
465                         mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
466                         break;
467                 }
468             }
469         };
470 
onBeginningOfSpeech()471         public void onBeginningOfSpeech() {
472             Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
473         }
474 
onBufferReceived(final byte[] buffer)475         public void onBufferReceived(final byte[] buffer) {
476             Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
477         }
478 
onEndOfSpeech()479         public void onEndOfSpeech() {
480             Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
481         }
482 
onError(final int error)483         public void onError(final int error) {
484             Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
485         }
486 
onReadyForSpeech(final Bundle noiseParams)487         public void onReadyForSpeech(final Bundle noiseParams) {
488             Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
489         }
490 
onResults(final Bundle results)491         public void onResults(final Bundle results) {
492             Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
493         }
494 
onPartialResults(final Bundle results)495         public void onPartialResults(final Bundle results) {
496             Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
497         }
498 
onRmsChanged(final float rmsdB)499         public void onRmsChanged(final float rmsdB) {
500             Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
501         }
502 
onEvent(final int eventType, final Bundle params)503         public void onEvent(final int eventType, final Bundle params) {
504             Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
505                     .sendToTarget();
506         }
507     }
508 }
509