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