1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.speech; 18 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.content.pm.ResolveInfo; 24import android.os.Bundle; 25import android.os.Handler; 26import android.os.IBinder; 27import android.os.Looper; 28import android.os.Message; 29import android.os.RemoteException; 30import android.provider.Settings; 31import android.text.TextUtils; 32import android.util.Log; 33 34import java.util.LinkedList; 35import java.util.List; 36import java.util.Queue; 37 38/** 39 * This class provides access to the speech recognition service. This service allows access to the 40 * speech recognizer. Do not instantiate this class directly, instead, call 41 * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be 42 * invoked only from the main application thread. Please note that the application must have 43 * {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class. 44 */ 45public class SpeechRecognizer { 46 /** DEBUG value to enable verbose debug prints */ 47 private final static boolean DBG = false; 48 49 /** Log messages identifier */ 50 private static final String TAG = "SpeechRecognizer"; 51 52 /** 53 * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the 54 * {@link RecognitionListener#onResults(Bundle)} and 55 * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible 56 * recognition results, where the first element is the most likely candidate. 57 */ 58 public static final String RESULTS_RECOGNITION = "results_recognition"; 59 60 /** 61 * Key used to retrieve a float array from the {@link Bundle} passed to the 62 * {@link RecognitionListener#onResults(Bundle)} and 63 * {@link RecognitionListener#onPartialResults(Bundle)} methods. The array should be 64 * the same size as the ArrayList provided in {@link #RESULTS_RECOGNITION}, and should contain 65 * values ranging from 0.0 to 1.0, or -1 to represent an unavailable confidence score. 66 * <p> 67 * Confidence values close to 1.0 indicate high confidence (the speech recognizer is confident 68 * that the recognition result is correct), while values close to 0.0 indicate low confidence. 69 * <p> 70 * This value is optional and might not be provided. 71 */ 72 public static final String CONFIDENCE_SCORES = "confidence_scores"; 73 74 /** Network operation timed out. */ 75 public static final int ERROR_NETWORK_TIMEOUT = 1; 76 77 /** Other network related errors. */ 78 public static final int ERROR_NETWORK = 2; 79 80 /** Audio recording error. */ 81 public static final int ERROR_AUDIO = 3; 82 83 /** Server sends error status. */ 84 public static final int ERROR_SERVER = 4; 85 86 /** Other client side errors. */ 87 public static final int ERROR_CLIENT = 5; 88 89 /** No speech input */ 90 public static final int ERROR_SPEECH_TIMEOUT = 6; 91 92 /** No recognition result matched. */ 93 public static final int ERROR_NO_MATCH = 7; 94 95 /** RecognitionService busy. */ 96 public static final int ERROR_RECOGNIZER_BUSY = 8; 97 98 /** Insufficient permissions */ 99 public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9; 100 101 /** action codes */ 102 private final static int MSG_START = 1; 103 private final static int MSG_STOP = 2; 104 private final static int MSG_CANCEL = 3; 105 private final static int MSG_CHANGE_LISTENER = 4; 106 107 /** The actual RecognitionService endpoint */ 108 private IRecognitionService mService; 109 110 /** The connection to the actual service */ 111 private Connection mConnection; 112 113 /** Context with which the manager was created */ 114 private final Context mContext; 115 116 /** Component to direct service intent to */ 117 private final ComponentName mServiceComponent; 118 119 /** Handler that will execute the main tasks */ 120 private Handler mHandler = new Handler() { 121 @Override 122 public void handleMessage(Message msg) { 123 switch (msg.what) { 124 case MSG_START: 125 handleStartListening((Intent) msg.obj); 126 break; 127 case MSG_STOP: 128 handleStopMessage(); 129 break; 130 case MSG_CANCEL: 131 handleCancelMessage(); 132 break; 133 case MSG_CHANGE_LISTENER: 134 handleChangeListener((RecognitionListener) msg.obj); 135 break; 136 } 137 } 138 }; 139 140 /** 141 * Temporary queue, saving the messages until the connection will be established, afterwards, 142 * only mHandler will receive the messages 143 */ 144 private final Queue<Message> mPendingTasks = new LinkedList<Message>(); 145 146 /** The Listener that will receive all the callbacks */ 147 private final InternalListener mListener = new InternalListener(); 148 149 /** 150 * The right way to create a {@code SpeechRecognizer} is by using 151 * {@link #createSpeechRecognizer} static factory method 152 */ 153 private SpeechRecognizer(final Context context, final ComponentName serviceComponent) { 154 mContext = context; 155 mServiceComponent = serviceComponent; 156 } 157 158 /** 159 * Basic ServiceConnection that records the mService variable. Additionally, on creation it 160 * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}. 161 */ 162 private class Connection implements ServiceConnection { 163 164 public void onServiceConnected(final ComponentName name, final IBinder service) { 165 // always done on the application main thread, so no need to send message to mHandler 166 mService = IRecognitionService.Stub.asInterface(service); 167 if (DBG) Log.d(TAG, "onServiceConnected - Success"); 168 while (!mPendingTasks.isEmpty()) { 169 mHandler.sendMessage(mPendingTasks.poll()); 170 } 171 } 172 173 public void onServiceDisconnected(final ComponentName name) { 174 // always done on the application main thread, so no need to send message to mHandler 175 mService = null; 176 mConnection = null; 177 mPendingTasks.clear(); 178 if (DBG) Log.d(TAG, "onServiceDisconnected - Success"); 179 } 180 } 181 182 /** 183 * Checks whether a speech recognition service is available on the system. If this method 184 * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will 185 * fail. 186 * 187 * @param context with which {@code SpeechRecognizer} will be created 188 * @return {@code true} if recognition is available, {@code false} otherwise 189 */ 190 public static boolean isRecognitionAvailable(final Context context) { 191 final List<ResolveInfo> list = context.getPackageManager().queryIntentServices( 192 new Intent(RecognitionService.SERVICE_INTERFACE), 0); 193 return list != null && list.size() != 0; 194 } 195 196 /** 197 * Factory method to create a new {@code SpeechRecognizer}. Please note that 198 * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any 199 * command to the created {@code SpeechRecognizer}, otherwise no notifications will be 200 * received. 201 * 202 * @param context in which to create {@code SpeechRecognizer} 203 * @return a new {@code SpeechRecognizer} 204 */ 205 public static SpeechRecognizer createSpeechRecognizer(final Context context) { 206 return createSpeechRecognizer(context, null); 207 } 208 209 /** 210 * Factory method to create a new {@code SpeechRecognizer}. Please note that 211 * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any 212 * command to the created {@code SpeechRecognizer}, otherwise no notifications will be 213 * received. 214 * 215 * Use this version of the method to specify a specific service to direct this 216 * {@link SpeechRecognizer} to. Normally you would not use this; use 217 * {@link #createSpeechRecognizer(Context)} instead to use the system default recognition 218 * service. 219 * 220 * @param context in which to create {@code SpeechRecognizer} 221 * @param serviceComponent the {@link ComponentName} of a specific service to direct this 222 * {@code SpeechRecognizer} to 223 * @return a new {@code SpeechRecognizer} 224 */ 225 public static SpeechRecognizer createSpeechRecognizer(final Context context, 226 final ComponentName serviceComponent) { 227 if (context == null) { 228 throw new IllegalArgumentException("Context cannot be null)"); 229 } 230 checkIsCalledFromMainThread(); 231 return new SpeechRecognizer(context, serviceComponent); 232 } 233 234 /** 235 * Sets the listener that will receive all the callbacks. The previous unfinished commands will 236 * be executed with the old listener, while any following command will be executed with the new 237 * listener. 238 * 239 * @param listener listener that will receive all the callbacks from the created 240 * {@link SpeechRecognizer}, this must not be null. 241 */ 242 public void setRecognitionListener(RecognitionListener listener) { 243 checkIsCalledFromMainThread(); 244 putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); 245 } 246 247 /** 248 * Starts listening for speech. Please note that 249 * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise 250 * no notifications will be received. 251 * 252 * @param recognizerIntent contains parameters for the recognition to be performed. The intent 253 * may also contain optional extras, see {@link RecognizerIntent}. If these values are 254 * not set explicitly, default values will be used by the recognizer. 255 */ 256 public void startListening(final Intent recognizerIntent) { 257 if (recognizerIntent == null) { 258 throw new IllegalArgumentException("intent must not be null"); 259 } 260 checkIsCalledFromMainThread(); 261 if (mConnection == null) { // first time connection 262 mConnection = new Connection(); 263 264 Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE); 265 266 if (mServiceComponent == null) { 267 String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(), 268 Settings.Secure.VOICE_RECOGNITION_SERVICE); 269 270 if (TextUtils.isEmpty(serviceComponent)) { 271 Log.e(TAG, "no selected voice recognition service"); 272 mListener.onError(ERROR_CLIENT); 273 return; 274 } 275 276 serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent)); 277 } else { 278 serviceIntent.setComponent(mServiceComponent); 279 } 280 281 if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) { 282 Log.e(TAG, "bind to recognition service failed"); 283 mConnection = null; 284 mService = null; 285 mListener.onError(ERROR_CLIENT); 286 return; 287 } 288 } 289 putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); 290 } 291 292 /** 293 * Stops listening for speech. Speech captured so far will be recognized as if the user had 294 * stopped speaking at this point. Note that in the default case, this does not need to be 295 * called, as the speech endpointer will automatically stop the recognizer listening when it 296 * determines speech has completed. However, you can manipulate endpointer parameters directly 297 * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes 298 * want to manually call this method to stop listening sooner. Please note that 299 * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise 300 * no notifications will be received. 301 */ 302 public void stopListening() { 303 checkIsCalledFromMainThread(); 304 putMessage(Message.obtain(mHandler, MSG_STOP)); 305 } 306 307 /** 308 * Cancels the speech recognition. Please note that 309 * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise 310 * no notifications will be received. 311 */ 312 public void cancel() { 313 checkIsCalledFromMainThread(); 314 putMessage(Message.obtain(mHandler, MSG_CANCEL)); 315 } 316 317 private static void checkIsCalledFromMainThread() { 318 if (Looper.myLooper() != Looper.getMainLooper()) { 319 throw new RuntimeException( 320 "SpeechRecognizer should be used only from the application's main thread"); 321 } 322 } 323 324 private void putMessage(Message msg) { 325 if (mService == null) { 326 mPendingTasks.offer(msg); 327 } else { 328 mHandler.sendMessage(msg); 329 } 330 } 331 332 /** sends the actual message to the service */ 333 private void handleStartListening(Intent recognizerIntent) { 334 if (!checkOpenConnection()) { 335 return; 336 } 337 try { 338 mService.startListening(recognizerIntent, mListener); 339 if (DBG) Log.d(TAG, "service start listening command succeded"); 340 } catch (final RemoteException e) { 341 Log.e(TAG, "startListening() failed", e); 342 mListener.onError(ERROR_CLIENT); 343 } 344 } 345 346 /** sends the actual message to the service */ 347 private void handleStopMessage() { 348 if (!checkOpenConnection()) { 349 return; 350 } 351 try { 352 mService.stopListening(mListener); 353 if (DBG) Log.d(TAG, "service stop listening command succeded"); 354 } catch (final RemoteException e) { 355 Log.e(TAG, "stopListening() failed", e); 356 mListener.onError(ERROR_CLIENT); 357 } 358 } 359 360 /** sends the actual message to the service */ 361 private void handleCancelMessage() { 362 if (!checkOpenConnection()) { 363 return; 364 } 365 try { 366 mService.cancel(mListener); 367 if (DBG) Log.d(TAG, "service cancel command succeded"); 368 } catch (final RemoteException e) { 369 Log.e(TAG, "cancel() failed", e); 370 mListener.onError(ERROR_CLIENT); 371 } 372 } 373 374 private boolean checkOpenConnection() { 375 if (mService != null) { 376 return true; 377 } 378 mListener.onError(ERROR_CLIENT); 379 Log.e(TAG, "not connected to the recognition service"); 380 return false; 381 } 382 383 /** changes the listener */ 384 private void handleChangeListener(RecognitionListener listener) { 385 if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener); 386 mListener.mInternalListener = listener; 387 } 388 389 /** 390 * Destroys the {@code SpeechRecognizer} object. 391 */ 392 public void destroy() { 393 if (mConnection != null) { 394 mContext.unbindService(mConnection); 395 } 396 mPendingTasks.clear(); 397 mService = null; 398 mConnection = null; 399 mListener.mInternalListener = null; 400 } 401 402 /** 403 * Internal wrapper of IRecognitionListener which will propagate the results to 404 * RecognitionListener 405 */ 406 private class InternalListener extends IRecognitionListener.Stub { 407 private RecognitionListener mInternalListener; 408 409 private final static int MSG_BEGINNING_OF_SPEECH = 1; 410 private final static int MSG_BUFFER_RECEIVED = 2; 411 private final static int MSG_END_OF_SPEECH = 3; 412 private final static int MSG_ERROR = 4; 413 private final static int MSG_READY_FOR_SPEECH = 5; 414 private final static int MSG_RESULTS = 6; 415 private final static int MSG_PARTIAL_RESULTS = 7; 416 private final static int MSG_RMS_CHANGED = 8; 417 private final static int MSG_ON_EVENT = 9; 418 419 private final Handler mInternalHandler = new Handler() { 420 @Override 421 public void handleMessage(Message msg) { 422 if (mInternalListener == null) { 423 return; 424 } 425 switch (msg.what) { 426 case MSG_BEGINNING_OF_SPEECH: 427 mInternalListener.onBeginningOfSpeech(); 428 break; 429 case MSG_BUFFER_RECEIVED: 430 mInternalListener.onBufferReceived((byte[]) msg.obj); 431 break; 432 case MSG_END_OF_SPEECH: 433 mInternalListener.onEndOfSpeech(); 434 break; 435 case MSG_ERROR: 436 mInternalListener.onError((Integer) msg.obj); 437 break; 438 case MSG_READY_FOR_SPEECH: 439 mInternalListener.onReadyForSpeech((Bundle) msg.obj); 440 break; 441 case MSG_RESULTS: 442 mInternalListener.onResults((Bundle) msg.obj); 443 break; 444 case MSG_PARTIAL_RESULTS: 445 mInternalListener.onPartialResults((Bundle) msg.obj); 446 break; 447 case MSG_RMS_CHANGED: 448 mInternalListener.onRmsChanged((Float) msg.obj); 449 break; 450 case MSG_ON_EVENT: 451 mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj); 452 break; 453 } 454 } 455 }; 456 457 public void onBeginningOfSpeech() { 458 Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget(); 459 } 460 461 public void onBufferReceived(final byte[] buffer) { 462 Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget(); 463 } 464 465 public void onEndOfSpeech() { 466 Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget(); 467 } 468 469 public void onError(final int error) { 470 Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget(); 471 } 472 473 public void onReadyForSpeech(final Bundle noiseParams) { 474 Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget(); 475 } 476 477 public void onResults(final Bundle results) { 478 Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget(); 479 } 480 481 public void onPartialResults(final Bundle results) { 482 Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget(); 483 } 484 485 public void onRmsChanged(final float rmsdB) { 486 Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget(); 487 } 488 489 public void onEvent(final int eventType, final Bundle params) { 490 Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params) 491 .sendToTarget(); 492 } 493 } 494} 495