RecognitionService.java revision 3da3cad97269d694a6153771fb4a0c3775ca6ab5
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.app.Service;
20import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.IBinder;
25import android.os.Message;
26import android.os.RemoteException;
27import android.util.Log;
28
29/**
30 * This class provides a base class for recognition service implementations. This class should be
31 * extended only in case you wish to implement a new speech recognizer. Please not that the
32 * implementation of this service is state-less.
33 */
34public abstract class RecognitionService extends Service {
35
36    /** Log messages identifier */
37    private static final String TAG = "RecognitionService";
38
39    /** Debugging flag */
40    private static final boolean DBG = false;
41
42    /**
43     * The current callback of an application that invoked the
44     * {@link RecognitionService#onStartListening(Intent, Callback)} method
45     */
46    private Callback mCurrentCallback = null;
47
48    private static final int MSG_START_LISTENING = 1;
49
50    private static final int MSG_STOP_LISTENING = 2;
51
52    private static final int MSG_CANCEL = 3;
53
54    private final Handler mHandler = new Handler() {
55        @Override
56        public void handleMessage(Message msg) {
57            switch (msg.what) {
58                case MSG_START_LISTENING:
59                    StartListeningArgs args = (StartListeningArgs) msg.obj;
60                    dispatchStartListening(args.mIntent, args.mListener);
61                    break;
62                case MSG_STOP_LISTENING:
63                    dispatchStopListening((IRecognitionListener) msg.obj);
64                    break;
65                case MSG_CANCEL:
66                    dispatchCancel((IRecognitionListener) msg.obj);
67            }
68        }
69    };
70
71    private void dispatchStartListening(Intent intent, IRecognitionListener listener) {
72        if (mCurrentCallback == null) {
73            if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
74            mCurrentCallback = new Callback(listener);
75            RecognitionService.this.onStartListening(intent, mCurrentCallback);
76        } else {
77            try {
78                listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
79            } catch (RemoteException e) {
80                Log.d(TAG, "onError call from startListening failed");
81            }
82            Log.i(TAG, "concurrent startListening received - ignoring this call");
83        }
84    }
85
86    private void dispatchStopListening(IRecognitionListener listener) {
87        try {
88            if (mCurrentCallback == null) {
89                listener.onError(RecognitionManager.ERROR_CLIENT);
90                Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
91            } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
92                listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
93                Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
94            } else { // the correct state
95                RecognitionService.this.onStopListening(mCurrentCallback);
96            }
97        } catch (RemoteException e) { // occurs if onError fails
98            Log.d(TAG, "onError call from stopListening failed");
99        }
100    }
101
102    private void dispatchCancel(IRecognitionListener listener) {
103        if (mCurrentCallback == null) {
104            Log.w(TAG, "cancel called with no preceding startListening - ignoring");
105        } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
106            Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
107        } else { // the correct state
108            RecognitionService.this.onCancel(mCurrentCallback);
109            mCurrentCallback = null;
110            if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
111        }
112    }
113
114    private class StartListeningArgs {
115        public final Intent mIntent;
116
117        public final IRecognitionListener mListener;
118
119        public StartListeningArgs(Intent intent, IRecognitionListener listener) {
120            this.mIntent = intent;
121            this.mListener = listener;
122        }
123    }
124
125    /** Binder of the recognition service */
126    private final IRecognitionService.Stub mBinder = new IRecognitionService.Stub() {
127        public void startListening(Intent recognizerIntent, IRecognitionListener listener) {
128            if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
129            if (checkPermissions(listener)) {
130                mHandler.sendMessage(Message.obtain(mHandler, MSG_START_LISTENING,
131                        new StartListeningArgs(recognizerIntent, listener)));
132            }
133        }
134
135        public void stopListening(IRecognitionListener listener) {
136            if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
137            if (checkPermissions(listener)) {
138                mHandler.sendMessage(Message.obtain(mHandler, MSG_STOP_LISTENING, listener));
139            }
140        }
141
142        public void cancel(IRecognitionListener listener) {
143            if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
144            if (checkPermissions(listener)) {
145                mHandler.sendMessage(Message.obtain(mHandler, MSG_CANCEL, listener));
146            }
147        }
148    };
149
150    /**
151     * Checks whether the caller has sufficient permissions
152     *
153     * @param listener to send the error message to in case of error
154     * @return {@code true} if the caller has enough permissions, {@code false} otherwise
155     */
156    private boolean checkPermissions(IRecognitionListener listener) {
157        if (DBG) Log.d(TAG, "checkPermissions");
158        if (RecognitionService.this.checkCallingOrSelfPermission(android.Manifest.permission.
159                RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
160            return true;
161        }
162        try {
163            Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions");
164            listener.onError(RecognitionManager.ERROR_INSUFFICIENT_PERMISSIONS);
165        } catch (RemoteException re) {
166            Log.e(TAG, "sending ERROR_INSUFFICIENT_PERMISSIONS message failed", re);
167        }
168        return false;
169    }
170
171    /**
172     * Notifies the service that it should start listening for speech.
173     *
174     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
175     *        may also contain optional extras, see {@link RecognizerIntent}. If these values are
176     *        not set explicitly, default values should be used by the recognizer.
177     * @param listener that will receive the service's callbacks
178     */
179    protected abstract void onStartListening(Intent recognizerIntent, Callback listener);
180
181    /**
182     * Notifies the service that it should cancel the speech recognition.
183     */
184    protected abstract void onCancel(Callback listener);
185
186    /**
187     * Notifies the service that it should stop listening for speech. Speech captured so far should
188     * be recognized as if the user had stopped speaking at this point. This method is only called
189     * if the application calls it explicitly.
190     */
191    protected abstract void onStopListening(Callback listener);
192
193    @Override
194    public final IBinder onBind(final Intent intent) {
195        if (DBG) Log.d(TAG, "onBind, intent=" + intent);
196        return mBinder;
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 RecognitionManager}
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 RecognitionManager#startListening(Intent)},
253         * depending on the speech recognition service implementation.
254         *
255         * @param partialResults the returned results. To retrieve the results in
256         *        ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
257         *        {@link RecognitionManager#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<String>} format use {@link Bundle#getStringArrayList(String)} with
278         *        {@link RecognitionManager#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