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