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.Manifest; 20 import android.annotation.IntDef; 21 import android.annotation.MainThread; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.TestApi; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.ResolveInfo; 30 import android.os.Binder; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.os.RemoteException; 37 import android.os.ServiceManager; 38 import android.provider.Settings; 39 import android.text.TextUtils; 40 import android.util.Log; 41 import android.util.Slog; 42 43 import com.android.internal.R; 44 45 import java.lang.annotation.Documented; 46 import java.lang.annotation.Retention; 47 import java.lang.annotation.RetentionPolicy; 48 import java.util.List; 49 import java.util.Queue; 50 import java.util.concurrent.LinkedBlockingQueue; 51 52 /** 53 * This class provides access to the speech recognition service. This service allows access to the 54 * speech recognizer. Do not instantiate this class directly, instead, call 55 * {@link SpeechRecognizer#createSpeechRecognizer(Context)}, or 56 * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be 57 * invoked only from the main application thread. 58 * 59 * <p>The implementation of this API is likely to stream audio to remote servers to perform speech 60 * recognition. As such this API is not intended to be used for continuous recognition, which would 61 * consume a significant amount of battery and bandwidth. 62 * 63 * <p>Please note that the application must have {@link android.Manifest.permission#RECORD_AUDIO} 64 * permission to use this class. 65 */ 66 public class SpeechRecognizer { 67 /** DEBUG value to enable verbose debug prints */ 68 private static final boolean DBG = false; 69 70 /** Log messages identifier */ 71 private static final String TAG = "SpeechRecognizer"; 72 73 /** 74 * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the 75 * {@link RecognitionListener#onResults(Bundle)} and 76 * {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible 77 * recognition results, where the first element is the most likely candidate. 78 */ 79 public static final String RESULTS_RECOGNITION = "results_recognition"; 80 81 /** 82 * Key used to retrieve a float array from the {@link Bundle} passed to the 83 * {@link RecognitionListener#onResults(Bundle)} and 84 * {@link RecognitionListener#onPartialResults(Bundle)} methods. The array should be 85 * the same size as the ArrayList provided in {@link #RESULTS_RECOGNITION}, and should contain 86 * values ranging from 0.0 to 1.0, or -1 to represent an unavailable confidence score. 87 * <p> 88 * Confidence values close to 1.0 indicate high confidence (the speech recognizer is confident 89 * that the recognition result is correct), while values close to 0.0 indicate low confidence. 90 * <p> 91 * This value is optional and might not be provided. 92 */ 93 public static final String CONFIDENCE_SCORES = "confidence_scores"; 94 95 /** 96 * The reason speech recognition failed. 97 * 98 * @hide 99 */ 100 @Documented 101 @Retention(RetentionPolicy.SOURCE) 102 @IntDef(prefix = {"ERROR_"}, value = { 103 ERROR_NETWORK_TIMEOUT, 104 ERROR_NETWORK, 105 ERROR_AUDIO, 106 ERROR_SERVER, 107 ERROR_CLIENT, 108 ERROR_SPEECH_TIMEOUT, 109 ERROR_NO_MATCH, 110 ERROR_RECOGNIZER_BUSY, 111 ERROR_INSUFFICIENT_PERMISSIONS, 112 ERROR_TOO_MANY_REQUESTS, 113 ERROR_SERVER_DISCONNECTED, 114 ERROR_LANGUAGE_NOT_SUPPORTED, 115 ERROR_LANGUAGE_UNAVAILABLE 116 }) 117 public @interface RecognitionError {} 118 119 /** Network operation timed out. */ 120 public static final int ERROR_NETWORK_TIMEOUT = 1; 121 122 /** Other network related errors. */ 123 public static final int ERROR_NETWORK = 2; 124 125 /** Audio recording error. */ 126 public static final int ERROR_AUDIO = 3; 127 128 /** Server sends error status. */ 129 public static final int ERROR_SERVER = 4; 130 131 /** Other client side errors. */ 132 public static final int ERROR_CLIENT = 5; 133 134 /** No speech input */ 135 public static final int ERROR_SPEECH_TIMEOUT = 6; 136 137 /** No recognition result matched. */ 138 public static final int ERROR_NO_MATCH = 7; 139 140 /** RecognitionService busy. */ 141 public static final int ERROR_RECOGNIZER_BUSY = 8; 142 143 /** Insufficient permissions */ 144 public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9; 145 146 /** Too many requests from the same client. */ 147 public static final int ERROR_TOO_MANY_REQUESTS = 10; 148 149 /** Server has been disconnected, e.g. because the app has crashed. */ 150 public static final int ERROR_SERVER_DISCONNECTED = 11; 151 152 /** Requested language is not available to be used with the current recognizer. */ 153 public static final int ERROR_LANGUAGE_NOT_SUPPORTED = 12; 154 155 /** Requested language is supported, but not available currently (e.g. not downloaded yet). */ 156 public static final int ERROR_LANGUAGE_UNAVAILABLE = 13; 157 158 /** action codes */ 159 private static final int MSG_START = 1; 160 private static final int MSG_STOP = 2; 161 private static final int MSG_CANCEL = 3; 162 private static final int MSG_CHANGE_LISTENER = 4; 163 private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5; 164 165 /** The actual RecognitionService endpoint */ 166 private IRecognitionService mService; 167 168 /** Context with which the manager was created */ 169 private final Context mContext; 170 171 /** Component to direct service intent to */ 172 private final ComponentName mServiceComponent; 173 174 /** Whether to use on-device speech recognizer. */ 175 private final boolean mOnDevice; 176 177 private IRecognitionServiceManager mManagerService; 178 179 /** Handler that will execute the main tasks */ 180 private Handler mHandler = new Handler(Looper.getMainLooper()) { 181 182 @Override 183 public void handleMessage(Message msg) { 184 switch (msg.what) { 185 case MSG_START: 186 handleStartListening((Intent) msg.obj); 187 break; 188 case MSG_STOP: 189 handleStopMessage(); 190 break; 191 case MSG_CANCEL: 192 handleCancelMessage(); 193 break; 194 case MSG_CHANGE_LISTENER: 195 handleChangeListener((RecognitionListener) msg.obj); 196 break; 197 case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT: 198 handleSetTemporaryComponent((ComponentName) msg.obj); 199 break; 200 } 201 } 202 }; 203 204 /** 205 * Temporary queue, saving the messages until the connection will be established, afterwards, 206 * only mHandler will receive the messages 207 */ 208 private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>(); 209 210 /** The Listener that will receive all the callbacks */ 211 private final InternalListener mListener = new InternalListener(); 212 213 private final IBinder mClientToken = new Binder(); 214 215 /** 216 * The right way to create a {@code SpeechRecognizer} is by using 217 * {@link #createSpeechRecognizer} static factory method 218 */ SpeechRecognizer(final Context context, final ComponentName serviceComponent)219 private SpeechRecognizer(final Context context, final ComponentName serviceComponent) { 220 mContext = context; 221 mServiceComponent = serviceComponent; 222 mOnDevice = false; 223 } 224 225 /** 226 * The right way to create a {@code SpeechRecognizer} is by using 227 * {@link #createOnDeviceSpeechRecognizer} static factory method 228 */ SpeechRecognizer(final Context context, boolean onDevice)229 private SpeechRecognizer(final Context context, boolean onDevice) { 230 mContext = context; 231 mServiceComponent = null; 232 mOnDevice = onDevice; 233 } 234 235 /** 236 * Checks whether a speech recognition service is available on the system. If this method 237 * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will 238 * fail. 239 * 240 * @param context with which {@code SpeechRecognizer} will be created 241 * @return {@code true} if recognition is available, {@code false} otherwise 242 */ isRecognitionAvailable(@onNull final Context context)243 public static boolean isRecognitionAvailable(@NonNull final Context context) { 244 // TODO(b/176578753): make sure this works well with system speech recognizers. 245 final List<ResolveInfo> list = context.getPackageManager().queryIntentServices( 246 new Intent(RecognitionService.SERVICE_INTERFACE), 0); 247 return list != null && list.size() != 0; 248 } 249 250 /** 251 * Checks whether an on-device speech recognition service is available on the system. If this 252 * method returns {@code false}, 253 * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)} will 254 * fail. 255 * 256 * @param context with which on-device {@code SpeechRecognizer} will be created 257 * @return {@code true} if on-device recognition is available, {@code false} otherwise 258 */ isOnDeviceRecognitionAvailable(@onNull final Context context)259 public static boolean isOnDeviceRecognitionAvailable(@NonNull final Context context) { 260 ComponentName componentName = 261 ComponentName.unflattenFromString( 262 context.getString(R.string.config_defaultOnDeviceSpeechRecognitionService)); 263 return componentName != null; 264 } 265 266 /** 267 * Factory method to create a new {@code SpeechRecognizer}. Please note that 268 * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any 269 * command to the created {@code SpeechRecognizer}, otherwise no notifications will be 270 * received. 271 * 272 * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition 273 * service requires <queries> element to be added to the manifest file: 274 * <pre>{@code 275 * <queries> 276 * <intent> 277 * <action 278 * android:name="android.speech.RecognitionService" /> 279 * </intent> 280 * </queries> 281 * }</pre> 282 * 283 * @param context in which to create {@code SpeechRecognizer} 284 * @return a new {@code SpeechRecognizer} 285 */ 286 @MainThread createSpeechRecognizer(final Context context)287 public static SpeechRecognizer createSpeechRecognizer(final Context context) { 288 return createSpeechRecognizer(context, null); 289 } 290 291 /** 292 * Factory method to create a new {@code SpeechRecognizer}. Please note that 293 * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any 294 * command to the created {@code SpeechRecognizer}, otherwise no notifications will be 295 * received. 296 * Use this version of the method to specify a specific service to direct this 297 * {@link SpeechRecognizer} to. 298 * 299 * <p><strong>Important</strong>: before calling this method, please check via 300 * {@link android.content.pm.PackageManager#queryIntentServices(Intent, int)} that {@code 301 * serviceComponent} actually exists and provides 302 * {@link RecognitionService#SERVICE_INTERFACE}. Normally you would not use this; call 303 * {@link #createSpeechRecognizer(Context)} to use the system default recognition 304 * service instead or {@link #createOnDeviceSpeechRecognizer(Context)} to use on-device 305 * recognition.</p> 306 * 307 * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition 308 * service requires <queries> element to be added to the manifest file: 309 * <pre>{@code 310 * <queries> 311 * <intent> 312 * <action 313 * android:name="android.speech.RecognitionService" /> 314 * </intent> 315 * </queries> 316 * }</pre> 317 * 318 * @param context in which to create {@code SpeechRecognizer} 319 * @param serviceComponent the {@link ComponentName} of a specific service to direct this 320 * {@code SpeechRecognizer} to 321 * @return a new {@code SpeechRecognizer} 322 */ 323 @MainThread createSpeechRecognizer(final Context context, final ComponentName serviceComponent)324 public static SpeechRecognizer createSpeechRecognizer(final Context context, 325 final ComponentName serviceComponent) { 326 if (context == null) { 327 throw new IllegalArgumentException("Context cannot be null"); 328 } 329 checkIsCalledFromMainThread(); 330 return new SpeechRecognizer(context, serviceComponent); 331 } 332 333 /** 334 * Factory method to create a new {@code SpeechRecognizer}. 335 * 336 * <p>Please note that {@link #setRecognitionListener(RecognitionListener)} should be called 337 * before dispatching any command to the created {@code SpeechRecognizer}, otherwise no 338 * notifications will be received. 339 * 340 * @param context in which to create {@code SpeechRecognizer} 341 * @return a new on-device {@code SpeechRecognizer}. 342 * @throws UnsupportedOperationException iff {@link #isOnDeviceRecognitionAvailable(Context)} 343 * is false 344 */ 345 @NonNull 346 @MainThread createOnDeviceSpeechRecognizer(@onNull final Context context)347 public static SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull final Context context) { 348 if (!isOnDeviceRecognitionAvailable(context)) { 349 throw new UnsupportedOperationException("On-device recognition is not available"); 350 } 351 return lenientlyCreateOnDeviceSpeechRecognizer(context); 352 } 353 354 /** 355 * Helper method to create on-device SpeechRecognizer in tests even when the device does not 356 * support on-device speech recognition. 357 * 358 * @hide 359 */ 360 @TestApi 361 @NonNull 362 @MainThread createOnDeviceTestingSpeechRecognizer( @onNull final Context context)363 public static SpeechRecognizer createOnDeviceTestingSpeechRecognizer( 364 @NonNull final Context context) { 365 return lenientlyCreateOnDeviceSpeechRecognizer(context); 366 } 367 368 @NonNull 369 @MainThread lenientlyCreateOnDeviceSpeechRecognizer( @onNull final Context context)370 private static SpeechRecognizer lenientlyCreateOnDeviceSpeechRecognizer( 371 @NonNull final Context context) { 372 if (context == null) { 373 throw new IllegalArgumentException("Context cannot be null"); 374 } 375 checkIsCalledFromMainThread(); 376 return new SpeechRecognizer(context, /* onDevice */ true); 377 } 378 379 /** 380 * Sets the listener that will receive all the callbacks. The previous unfinished commands will 381 * be executed with the old listener, while any following command will be executed with the new 382 * listener. 383 * 384 * @param listener listener that will receive all the callbacks from the created 385 * {@link SpeechRecognizer}, this must not be null. 386 */ 387 @MainThread setRecognitionListener(RecognitionListener listener)388 public void setRecognitionListener(RecognitionListener listener) { 389 checkIsCalledFromMainThread(); 390 putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); 391 } 392 393 /** 394 * Starts listening for speech. Please note that 395 * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise 396 * no notifications will be received. 397 * 398 * @param recognizerIntent contains parameters for the recognition to be performed. The intent 399 * may also contain optional extras, see {@link RecognizerIntent}. If these values are 400 * not set explicitly, default values will be used by the recognizer. 401 */ 402 @MainThread startListening(final Intent recognizerIntent)403 public void startListening(final Intent recognizerIntent) { 404 if (recognizerIntent == null) { 405 throw new IllegalArgumentException("intent must not be null"); 406 } 407 checkIsCalledFromMainThread(); 408 409 if (DBG) { 410 Slog.i(TAG, "#startListening called"); 411 if (mService == null) { 412 Slog.i(TAG, "Connection is not established yet"); 413 } 414 } 415 416 if (mService == null) { 417 // First time connection: first establish a connection, then dispatch #startListening. 418 connectToSystemService(); 419 } 420 putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); 421 } 422 423 /** 424 * Stops listening for speech. Speech captured so far will be recognized as if the user had 425 * stopped speaking at this point. 426 * 427 * <p>Note that in the default case, this does not need to be called, as the speech endpointer 428 * will automatically stop the recognizer listening when it determines speech has completed. 429 * However, you can manipulate endpointer parameters directly using the intent extras defined in 430 * {@link RecognizerIntent}, in which case you may sometimes want to manually call this method 431 * to stop listening sooner. 432 * 433 * <p>Upon invocation clients must wait until {@link RecognitionListener#onResults} or 434 * {@link RecognitionListener#onError} are invoked before calling 435 * {@link SpeechRecognizer#startListening} again. Otherwise such an attempt would be rejected by 436 * recognition service. 437 * 438 * <p>Please note that 439 * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise 440 * no notifications will be received. 441 */ 442 @MainThread stopListening()443 public void stopListening() { 444 checkIsCalledFromMainThread(); 445 446 if (DBG) { 447 Slog.i(TAG, "#stopListening called"); 448 if (mService == null) { 449 Slog.i(TAG, "Connection is not established yet"); 450 } 451 } 452 453 putMessage(Message.obtain(mHandler, MSG_STOP)); 454 } 455 456 /** 457 * Cancels the speech recognition. Please note that 458 * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise 459 * no notifications will be received. 460 */ 461 @MainThread cancel()462 public void cancel() { 463 checkIsCalledFromMainThread(); 464 putMessage(Message.obtain(mHandler, MSG_CANCEL)); 465 } 466 467 /** 468 * Sets a temporary component to power on-device speech recognizer. 469 * 470 * <p>This is only expected to be called in tests, system would reject calls from client apps. 471 * 472 * @param componentName name of the component to set temporary replace speech recognizer. {@code 473 * null} value resets the recognizer to default. 474 * 475 * @hide 476 */ 477 @TestApi 478 @RequiresPermission(Manifest.permission.MANAGE_SPEECH_RECOGNITION) setTemporaryOnDeviceRecognizer(@ullable ComponentName componentName)479 public void setTemporaryOnDeviceRecognizer(@Nullable ComponentName componentName) { 480 mHandler.sendMessage( 481 Message.obtain(mHandler, MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT, componentName)); 482 } 483 checkIsCalledFromMainThread()484 private static void checkIsCalledFromMainThread() { 485 if (Looper.myLooper() != Looper.getMainLooper()) { 486 throw new RuntimeException( 487 "SpeechRecognizer should be used only from the application's main thread"); 488 } 489 } 490 putMessage(Message msg)491 private void putMessage(Message msg) { 492 if (mService == null) { 493 mPendingTasks.offer(msg); 494 } else { 495 mHandler.sendMessage(msg); 496 } 497 } 498 499 /** sends the actual message to the service */ handleStartListening(Intent recognizerIntent)500 private void handleStartListening(Intent recognizerIntent) { 501 if (!checkOpenConnection()) { 502 return; 503 } 504 try { 505 mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); 506 if (DBG) Log.d(TAG, "service start listening command succeded"); 507 } catch (final RemoteException e) { 508 Log.e(TAG, "startListening() failed", e); 509 mListener.onError(ERROR_CLIENT); 510 } 511 } 512 513 /** sends the actual message to the service */ handleStopMessage()514 private void handleStopMessage() { 515 if (!checkOpenConnection()) { 516 return; 517 } 518 try { 519 mService.stopListening(mListener); 520 if (DBG) Log.d(TAG, "service stop listening command succeded"); 521 } catch (final RemoteException e) { 522 Log.e(TAG, "stopListening() failed", e); 523 mListener.onError(ERROR_CLIENT); 524 } 525 } 526 527 /** sends the actual message to the service */ handleCancelMessage()528 private void handleCancelMessage() { 529 if (!checkOpenConnection()) { 530 return; 531 } 532 try { 533 mService.cancel(mListener, /*isShutdown*/ false); 534 if (DBG) Log.d(TAG, "service cancel command succeded"); 535 } catch (final RemoteException e) { 536 Log.e(TAG, "cancel() failed", e); 537 mListener.onError(ERROR_CLIENT); 538 } 539 } 540 handleSetTemporaryComponent(ComponentName componentName)541 private void handleSetTemporaryComponent(ComponentName componentName) { 542 if (DBG) { 543 Log.d(TAG, "handleSetTemporaryComponent, componentName=" + componentName); 544 } 545 546 if (!maybeInitializeManagerService()) { 547 return; 548 } 549 550 try { 551 mManagerService.setTemporaryComponent(componentName); 552 } catch (final RemoteException e) { 553 e.rethrowFromSystemServer(); 554 } 555 } 556 checkOpenConnection()557 private boolean checkOpenConnection() { 558 if (mService != null) { 559 return true; 560 } 561 mListener.onError(ERROR_CLIENT); 562 Log.e(TAG, "not connected to the recognition service"); 563 return false; 564 } 565 566 /** changes the listener */ handleChangeListener(RecognitionListener listener)567 private void handleChangeListener(RecognitionListener listener) { 568 if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener); 569 mListener.mInternalListener = listener; 570 } 571 572 /** Destroys the {@code SpeechRecognizer} object. */ destroy()573 public void destroy() { 574 if (mService != null) { 575 try { 576 mService.cancel(mListener, /*isShutdown*/ true); 577 } catch (final RemoteException e) { 578 // Not important 579 } 580 } 581 582 mService = null; 583 mPendingTasks.clear(); 584 mListener.mInternalListener = null; 585 } 586 587 /** Establishes a connection to system server proxy and initializes the session. */ connectToSystemService()588 private void connectToSystemService() { 589 if (!maybeInitializeManagerService()) { 590 return; 591 } 592 593 ComponentName componentName = getSpeechRecognizerComponentName(); 594 595 if (!mOnDevice && componentName == null) { 596 mListener.onError(ERROR_CLIENT); 597 return; 598 } 599 600 try { 601 mManagerService.createSession( 602 componentName, 603 mClientToken, 604 mOnDevice, 605 new IRecognitionServiceManagerCallback.Stub(){ 606 @Override 607 public void onSuccess(IRecognitionService service) throws RemoteException { 608 if (DBG) { 609 Log.i(TAG, "Connected to speech recognition service"); 610 } 611 mService = service; 612 while (!mPendingTasks.isEmpty()) { 613 mHandler.sendMessage(mPendingTasks.poll()); 614 } 615 } 616 617 @Override 618 public void onError(int errorCode) throws RemoteException { 619 Log.e(TAG, "Bind to system recognition service failed with error " 620 + errorCode); 621 mListener.onError(errorCode); 622 } 623 }); 624 } catch (RemoteException e) { 625 e.rethrowFromSystemServer(); 626 } 627 } 628 maybeInitializeManagerService()629 private boolean maybeInitializeManagerService() { 630 if (mManagerService != null) { 631 return true; 632 } 633 634 mManagerService = IRecognitionServiceManager.Stub.asInterface( 635 ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); 636 637 if (mManagerService == null && mListener != null) { 638 mListener.onError(ERROR_CLIENT); 639 return false; 640 } 641 return true; 642 } 643 644 /** 645 * Returns the component name to be used for establishing a connection, based on the parameters 646 * used during initialization. 647 * 648 * <p>Note the 3 different scenarios: 649 * <ol> 650 * <li>On-device speech recognizer which is determined by the manufacturer and not 651 * changeable by the user 652 * <li>Default user-selected speech recognizer as specified by 653 * {@code Settings.Secure.VOICE_RECOGNITION_SERVICE} 654 * <li>Custom speech recognizer supplied by the client. 655 */ getSpeechRecognizerComponentName()656 private ComponentName getSpeechRecognizerComponentName() { 657 if (mOnDevice) { 658 return null; 659 } 660 661 if (mServiceComponent != null) { 662 return mServiceComponent; 663 } 664 665 String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(), 666 Settings.Secure.VOICE_RECOGNITION_SERVICE); 667 668 if (TextUtils.isEmpty(serviceComponent)) { 669 Log.e(TAG, "no selected voice recognition service"); 670 mListener.onError(ERROR_CLIENT); 671 return null; 672 } 673 674 return ComponentName.unflattenFromString(serviceComponent); 675 } 676 677 /** 678 * Internal wrapper of IRecognitionListener which will propagate the results to 679 * RecognitionListener 680 */ 681 private static class InternalListener extends IRecognitionListener.Stub { 682 private RecognitionListener mInternalListener; 683 684 private static final int MSG_BEGINNING_OF_SPEECH = 1; 685 private static final int MSG_BUFFER_RECEIVED = 2; 686 private static final int MSG_END_OF_SPEECH = 3; 687 private static final int MSG_ERROR = 4; 688 private static final int MSG_READY_FOR_SPEECH = 5; 689 private static final int MSG_RESULTS = 6; 690 private static final int MSG_PARTIAL_RESULTS = 7; 691 private static final int MSG_RMS_CHANGED = 8; 692 private static final int MSG_ON_EVENT = 9; 693 694 private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { 695 @Override 696 public void handleMessage(Message msg) { 697 if (mInternalListener == null) { 698 return; 699 } 700 switch (msg.what) { 701 case MSG_BEGINNING_OF_SPEECH: 702 mInternalListener.onBeginningOfSpeech(); 703 break; 704 case MSG_BUFFER_RECEIVED: 705 mInternalListener.onBufferReceived((byte[]) msg.obj); 706 break; 707 case MSG_END_OF_SPEECH: 708 mInternalListener.onEndOfSpeech(); 709 break; 710 case MSG_ERROR: 711 mInternalListener.onError((Integer) msg.obj); 712 break; 713 case MSG_READY_FOR_SPEECH: 714 mInternalListener.onReadyForSpeech((Bundle) msg.obj); 715 break; 716 case MSG_RESULTS: 717 mInternalListener.onResults((Bundle) msg.obj); 718 break; 719 case MSG_PARTIAL_RESULTS: 720 mInternalListener.onPartialResults((Bundle) msg.obj); 721 break; 722 case MSG_RMS_CHANGED: 723 mInternalListener.onRmsChanged((Float) msg.obj); 724 break; 725 case MSG_ON_EVENT: 726 mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj); 727 break; 728 } 729 } 730 }; 731 onBeginningOfSpeech()732 public void onBeginningOfSpeech() { 733 Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget(); 734 } 735 onBufferReceived(final byte[] buffer)736 public void onBufferReceived(final byte[] buffer) { 737 Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget(); 738 } 739 onEndOfSpeech()740 public void onEndOfSpeech() { 741 Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget(); 742 } 743 onError(final int error)744 public void onError(final int error) { 745 Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget(); 746 } 747 onReadyForSpeech(final Bundle noiseParams)748 public void onReadyForSpeech(final Bundle noiseParams) { 749 Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget(); 750 } 751 onResults(final Bundle results)752 public void onResults(final Bundle results) { 753 Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget(); 754 } 755 onPartialResults(final Bundle results)756 public void onPartialResults(final Bundle results) { 757 Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget(); 758 } 759 onRmsChanged(final float rmsdB)760 public void onRmsChanged(final float rmsdB) { 761 Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget(); 762 } 763 onEvent(final int eventType, final Bundle params)764 public void onEvent(final int eventType, final Bundle params) { 765 Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params) 766 .sendToTarget(); 767 } 768 } 769 } 770