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