SpeechRecognizer.java revision c1fb6dc1a494d73a080348d16b96e70f5735e036
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/*
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Copyright (C) 2010 The Android Open Source Project
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Licensed under the Apache License, Version 2.0 (the "License");
5bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch * you may not use this file except in compliance with the License.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * You may obtain a copy of the License at
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
8eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch *      http://www.apache.org/licenses/LICENSE-2.0
9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) *
10ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch * Unless required by applicable law or agreed to in writing, software
11868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS,
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * See the License for the specific language governing permissions and
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * limitations under the License.
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
177d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)package android.speech;
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.ComponentName;
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.Context;
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.Intent;
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.ServiceConnection;
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.content.pm.ResolveInfo;
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.Bundle;
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.Handler;
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.IBinder;
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.Looper;
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.Message;
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.os.RemoteException;
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import android.provider.Settings;
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import android.text.TextUtils;
32c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import android.util.Log;
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.LinkedList;
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.List;
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.Queue;
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This class provides access to the speech recognition service. This service allows access to the
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * speech recognizer. Do not instantiate this class directly, instead, call
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * invoked only from the main application thread.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>The implementation of this API is likely to stream audio to remote servers to perform speech
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * recognition. As such this API is not intended to be used for continuous recognition, which would
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * consume a significant amount of battery and bandwidth.
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * <p>Please note that the application must have {@link android.Manifest.permission#RECORD_AUDIO}
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * permission to use this class.
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)public class SpeechRecognizer {
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** DEBUG value to enable verbose debug prints */
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final static boolean DBG = false;
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Log messages identifier */
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private static final String TAG = "SpeechRecognizer";
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * {@link RecognitionListener#onResults(Bundle)} and
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * recognition results, where the first element is the most likely candidate.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final String RESULTS_RECOGNITION = "results_recognition";
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Key used to retrieve a float array from the {@link Bundle} passed to the
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * {@link RecognitionListener#onResults(Bundle)} and
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * {@link RecognitionListener#onPartialResults(Bundle)} methods. The array should be
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * the same size as the ArrayList provided in {@link #RESULTS_RECOGNITION}, and should contain
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * values ranging from 0.0 to 1.0, or -1 to represent an unavailable confidence score.
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * <p>
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Confidence values close to 1.0 indicate high confidence (the speech recognizer is confident
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * that the recognition result is correct), while values close to 0.0 indicate low confidence.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * <p>
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * This value is optional and might not be provided.
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final String CONFIDENCE_SCORES = "confidence_scores";
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Network operation timed out. */
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_NETWORK_TIMEOUT = 1;
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Other network related errors. */
84ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    public static final int ERROR_NETWORK = 2;
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Audio recording error. */
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_AUDIO = 3;
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Server sends error status. */
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_SERVER = 4;
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Other client side errors. */
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_CLIENT = 5;
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** No speech input */
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_SPEECH_TIMEOUT = 6;
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** No recognition result matched. */
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_NO_MATCH = 7;
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** RecognitionService busy. */
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_RECOGNIZER_BUSY = 8;
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Insufficient permissions */
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** action codes */
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final static int MSG_START = 1;
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private final static int MSG_STOP = 2;
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    private final static int MSG_CANCEL = 3;
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final static int MSG_CHANGE_LISTENER = 4;
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** The actual RecognitionService endpoint */
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private IRecognitionService mService;
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** The connection to the actual service */
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private Connection mConnection;
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Context with which the manager was created */
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final Context mContext;
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Component to direct service intent to */
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final ComponentName mServiceComponent;
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** Handler that will execute the main tasks */
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private Handler mHandler = new Handler() {
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        @Override
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        public void handleMessage(Message msg) {
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            switch (msg.what) {
130f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                case MSG_START:
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    handleStartListening((Intent) msg.obj);
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    break;
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                case MSG_STOP:
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    handleStopMessage();
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    break;
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                case MSG_CANCEL:
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    handleCancelMessage();
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    break;
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                case MSG_CHANGE_LISTENER:
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    handleChangeListener((RecognitionListener) msg.obj);
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    break;
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            }
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    };
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Temporary queue, saving the messages until the connection will be established, afterwards,
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * only mHandler will receive the messages
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final Queue<Message> mPendingTasks = new LinkedList<Message>();
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /** The Listener that will receive all the callbacks */
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private final InternalListener mListener = new InternalListener();
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    /**
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * The right way to create a {@code SpeechRecognizer} is by using
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * {@link #createSpeechRecognizer} static factory method
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mContext = context;
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mServiceComponent = serviceComponent;
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Basic ServiceConnection that records the mService variable. Additionally, on creation it
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    private class Connection implements ServiceConnection {
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
318    public void cancel() {
319        checkIsCalledFromMainThread();
320        putMessage(Message.obtain(mHandler, MSG_CANCEL));
321    }
322
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
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 */
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 */
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 */
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
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 */
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     */
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
471        public void onBeginningOfSpeech() {
472            Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
473        }
474
475        public void onBufferReceived(final byte[] buffer) {
476            Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
477        }
478
479        public void onEndOfSpeech() {
480            Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
481        }
482
483        public void onError(final int error) {
484            Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
485        }
486
487        public void onReadyForSpeech(final Bundle noiseParams) {
488            Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
489        }
490
491        public void onResults(final Bundle results) {
492            Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
493        }
494
495        public void onPartialResults(final Bundle results) {
496            Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
497        }
498
499        public void onRmsChanged(final float rmsdB) {
500            Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
501        }
502
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