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
17package android.speech;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.content.pm.ResolveInfo;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.Message;
29import android.os.RemoteException;
30import android.provider.Settings;
31import android.text.TextUtils;
32import android.util.Log;
33
34import java.util.LinkedList;
35import java.util.List;
36import 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 */
45public 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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
312    public void cancel() {
313        checkIsCalledFromMainThread();
314        putMessage(Message.obtain(mHandler, MSG_CANCEL));
315    }
316
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
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 */
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 */
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 */
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
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 */
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     */
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
457        public void onBeginningOfSpeech() {
458            Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
459        }
460
461        public void onBufferReceived(final byte[] buffer) {
462            Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
463        }
464
465        public void onEndOfSpeech() {
466            Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
467        }
468
469        public void onError(final int error) {
470            Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
471        }
472
473        public void onReadyForSpeech(final Bundle noiseParams) {
474            Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
475        }
476
477        public void onResults(final Bundle results) {
478            Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
479        }
480
481        public void onPartialResults(final Bundle results) {
482            Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
483        }
484
485        public void onRmsChanged(final float rmsdB) {
486            Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
487        }
488
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