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