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><{@link android.R.styleable#RecognitionService recognition-service}></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<String> 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<String>} 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