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