RecognitionService.java revision 1c3cca0abed55516d2c67f2f11fc888a6a66f341
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.speech;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.app.Service;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Message;
28import android.os.RemoteException;
29import android.util.Log;
30
31/**
32 * This class provides a base class for recognition service implementations. This class should be
33 * extended only in case you wish to implement a new speech recognizer. Please note that the
34 * implementation of this service is stateless.
35 */
36public abstract class RecognitionService extends Service {
37    /**
38     * The {@link Intent} that must be declared as handled by the service.
39     */
40    @SdkConstant(SdkConstantType.SERVICE_ACTION)
41    public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
42
43    /**
44     * Name under which a RecognitionService component publishes information about itself.
45     * This meta-data should reference an XML resource containing a
46     * <code>&lt;{@link android.R.styleable#RecognitionService recognition-service}&gt;</code> tag.
47     */
48    public static final String SERVICE_META_DATA = "android.speech";
49
50    /** Log messages identifier */
51    private static final String TAG = "RecognitionService";
52
53    /** Debugging flag */
54    private static final boolean DBG = false;
55
56    /** Binder of the recognition service */
57    private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
58
59    /**
60     * The current callback of an application that invoked the
61     * {@link RecognitionService#onStartListening(Intent, Callback)} method
62     */
63    private Callback mCurrentCallback = null;
64
65    private static final int MSG_START_LISTENING = 1;
66
67    private static final int MSG_STOP_LISTENING = 2;
68
69    private static final int MSG_CANCEL = 3;
70
71    private final Handler mHandler = new Handler() {
72        @Override
73        public void handleMessage(Message msg) {
74            switch (msg.what) {
75                case MSG_START_LISTENING:
76                    StartListeningArgs args = (StartListeningArgs) msg.obj;
77                    dispatchStartListening(args.mIntent, args.mListener);
78                    break;
79                case MSG_STOP_LISTENING:
80                    dispatchStopListening((IRecognitionListener) msg.obj);
81                    break;
82                case MSG_CANCEL:
83                    dispatchCancel((IRecognitionListener) msg.obj);
84            }
85        }
86    };
87
88    private void dispatchStartListening(Intent intent, IRecognitionListener listener) {
89        if (mCurrentCallback == null) {
90            if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
91            mCurrentCallback = new Callback(listener);
92            RecognitionService.this.onStartListening(intent, mCurrentCallback);
93        } else {
94            try {
95                listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
96            } catch (RemoteException e) {
97                Log.d(TAG, "onError call from startListening failed");
98            }
99            Log.i(TAG, "concurrent startListening received - ignoring this call");
100        }
101    }
102
103    private void dispatchStopListening(IRecognitionListener listener) {
104        try {
105            if (mCurrentCallback == null) {
106                listener.onError(SpeechRecognizer.ERROR_CLIENT);
107                Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
108            } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
109                listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
110                Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
111            } else { // the correct state
112                RecognitionService.this.onStopListening(mCurrentCallback);
113            }
114        } catch (RemoteException e) { // occurs if onError fails
115            Log.d(TAG, "onError call from stopListening failed");
116        }
117    }
118
119    private void dispatchCancel(IRecognitionListener listener) {
120        if (mCurrentCallback == null) {
121            Log.w(TAG, "cancel called with no preceding startListening - ignoring");
122        } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
123            Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
124        } else { // the correct state
125            RecognitionService.this.onCancel(mCurrentCallback);
126            mCurrentCallback = null;
127            if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
128        }
129    }
130
131    private class StartListeningArgs {
132        public final Intent mIntent;
133
134        public final IRecognitionListener mListener;
135
136        public StartListeningArgs(Intent intent, IRecognitionListener listener) {
137            this.mIntent = intent;
138            this.mListener = listener;
139        }
140    }
141
142    /**
143     * Checks whether the caller has sufficient permissions
144     *
145     * @param listener to send the error message to in case of error
146     * @return {@code true} if the caller has enough permissions, {@code false} otherwise
147     */
148    private boolean checkPermissions(IRecognitionListener listener) {
149        if (DBG) Log.d(TAG, "checkPermissions");
150        if (RecognitionService.this.checkCallingOrSelfPermission(android.Manifest.permission.
151                RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
152            return true;
153        }
154        try {
155            Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions");
156            listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
157        } catch (RemoteException re) {
158            Log.e(TAG, "sending ERROR_INSUFFICIENT_PERMISSIONS message failed", re);
159        }
160        return false;
161    }
162
163    /**
164     * Notifies the service that it should start listening for speech.
165     *
166     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
167     *        may also contain optional extras, see {@link RecognizerIntent}. If these values are
168     *        not set explicitly, default values should be used by the recognizer.
169     * @param listener that will receive the service's callbacks
170     */
171    protected abstract void onStartListening(Intent recognizerIntent, Callback listener);
172
173    /**
174     * Notifies the service that it should cancel the speech recognition.
175     */
176    protected abstract void onCancel(Callback listener);
177
178    /**
179     * Notifies the service that it should stop listening for speech. Speech captured so far should
180     * be recognized as if the user had stopped speaking at this point. This method is only called
181     * if the application calls it explicitly.
182     */
183    protected abstract void onStopListening(Callback listener);
184
185    @Override
186    public final IBinder onBind(final Intent intent) {
187        if (DBG) Log.d(TAG, "onBind, intent=" + intent);
188        return mBinder;
189    }
190
191    @Override
192    public void onDestroy() {
193        if (DBG) Log.d(TAG, "onDestroy");
194        mCurrentCallback = null;
195        mBinder.clearReference();
196        super.onDestroy();
197    }
198
199    /**
200     * This class receives callbacks from the speech recognition service and forwards them to the
201     * user. An instance of this class is passed to the
202     * {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
203     * these methods on any thread.
204     */
205    public class Callback {
206        private final IRecognitionListener mListener;
207
208        private Callback(IRecognitionListener listener) {
209            mListener = listener;
210        }
211
212        /**
213         * The service should call this method when the user has started to speak.
214         */
215        public void beginningOfSpeech() throws RemoteException {
216            if (DBG) Log.d(TAG, "beginningOfSpeech");
217            mListener.onBeginningOfSpeech();
218        }
219
220        /**
221         * The service should call this method when sound has been received. The purpose of this
222         * function is to allow giving feedback to the user regarding the captured audio.
223         *
224         * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
225         *        single channel audio stream. The sample rate is implementation dependent.
226         */
227        public void bufferReceived(byte[] buffer) throws RemoteException {
228            mListener.onBufferReceived(buffer);
229        }
230
231        /**
232         * The service should call this method after the user stops speaking.
233         */
234        public void endOfSpeech() throws RemoteException {
235            mListener.onEndOfSpeech();
236        }
237
238        /**
239         * The service should call this method when a network or recognition error occurred.
240         *
241         * @param error code is defined in {@link SpeechRecognizer}
242         */
243        public void error(int error) throws RemoteException {
244            mCurrentCallback = null;
245            mListener.onError(error);
246        }
247
248        /**
249         * The service should call this method when partial recognition results are available. This
250         * method can be called at any time between {@link #beginningOfSpeech()} and
251         * {@link #results(Bundle)} when partial results are ready. This method may be called zero,
252         * one or multiple times for each call to {@link SpeechRecognizer#startListening(Intent)},
253         * depending on the speech recognition service implementation.
254         *
255         * @param partialResults the returned results. To retrieve the results in
256         *        ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
257         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
258         */
259        public void partialResults(Bundle partialResults) throws RemoteException {
260            mListener.onPartialResults(partialResults);
261        }
262
263        /**
264         * The service should call this method when the endpointer is ready for the user to start
265         * speaking.
266         *
267         * @param params parameters set by the recognition service. Reserved for future use.
268         */
269        public void readyForSpeech(Bundle params) throws RemoteException {
270            mListener.onReadyForSpeech(params);
271        }
272
273        /**
274         * The service should call this method when recognition results are ready.
275         *
276         * @param results the recognition results. To retrieve the results in {@code
277         *        ArrayList&lt;String&gt;} format use {@link Bundle#getStringArrayList(String)} with
278         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
279         */
280        public void results(Bundle results) throws RemoteException {
281            mCurrentCallback = null;
282            mListener.onResults(results);
283        }
284
285        /**
286         * The service should call this method when the sound level in the audio stream has changed.
287         * There is no guarantee that this method will be called.
288         *
289         * @param rmsdB the new RMS dB value
290         */
291        public void rmsChanged(float rmsdB) throws RemoteException {
292            mListener.onRmsChanged(rmsdB);
293        }
294    }
295
296    /** Binder of the recognition service */
297    private static class RecognitionServiceBinder extends IRecognitionService.Stub {
298        private RecognitionService mInternalService;
299
300        public RecognitionServiceBinder(RecognitionService service) {
301            mInternalService = service;
302        }
303
304        public void startListening(Intent recognizerIntent, IRecognitionListener listener) {
305            if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
306            if (mInternalService != null && mInternalService.checkPermissions(listener)) {
307                mInternalService.mHandler.sendMessage(Message.obtain(mInternalService.mHandler,
308                        MSG_START_LISTENING, mInternalService.new StartListeningArgs(
309                                recognizerIntent, listener)));
310            }
311        }
312
313        public void stopListening(IRecognitionListener listener) {
314            if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
315            if (mInternalService != null && mInternalService.checkPermissions(listener)) {
316                mInternalService.mHandler.sendMessage(Message.obtain(mInternalService.mHandler,
317                        MSG_STOP_LISTENING, listener));
318            }
319        }
320
321        public void cancel(IRecognitionListener listener) {
322            if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
323            if (mInternalService != null && mInternalService.checkPermissions(listener)) {
324                mInternalService.mHandler.sendMessage(Message.obtain(mInternalService.mHandler,
325                        MSG_CANCEL, listener));
326            }
327        }
328
329        public void clearReference() {
330            mInternalService = null;
331        }
332    }
333}
334