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