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