AlwaysOnHotwordDetector.java revision 668327d0286591324fa7592ee9b39255076e2165
1/** 2 * Copyright (C) 2014 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.service.voice; 18 19import android.content.Intent; 20import android.hardware.soundtrigger.IRecognitionStatusCallback; 21import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; 22import android.hardware.soundtrigger.KeyphraseMetadata; 23import android.hardware.soundtrigger.SoundTrigger; 24import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel; 25import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 26import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; 27import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 28import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 29import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 30import android.os.AsyncTask; 31import android.os.Handler; 32import android.os.Message; 33import android.os.RemoteException; 34import android.util.Slog; 35 36import com.android.internal.app.IVoiceInteractionManagerService; 37 38/** 39 * A class that lets a VoiceInteractionService implementation interact with 40 * always-on keyphrase detection APIs. 41 */ 42public class AlwaysOnHotwordDetector { 43 //---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----// 44 /** 45 * Indicates that this hotword detector is no longer valid for any recognition 46 * and should not be used anymore. 47 */ 48 private static final int STATE_INVALID = -3; 49 50 /** 51 * Indicates that recognition for the given keyphrase is not available on the system 52 * because of the hardware configuration. 53 */ 54 public static final int STATE_HARDWARE_UNAVAILABLE = -2; 55 /** 56 * Indicates that recognition for the given keyphrase is not supported. 57 */ 58 public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; 59 /** 60 * Indicates that the given keyphrase is not enrolled. 61 */ 62 public static final int STATE_KEYPHRASE_UNENROLLED = 1; 63 /** 64 * Indicates that the given keyphrase is currently enrolled and it's possible to start 65 * recognition for it. 66 */ 67 public static final int STATE_KEYPHRASE_ENROLLED = 2; 68 69 /** 70 * Indicates that the detector isn't ready currently. 71 */ 72 private static final int STATE_NOT_READY = 0; 73 74 // Keyphrase management actions. Used in getManageIntent() ----// 75 /** Indicates that we need to enroll. */ 76 public static final int MANAGE_ACTION_ENROLL = 0; 77 /** Indicates that we need to re-enroll. */ 78 public static final int MANAGE_ACTION_RE_ENROLL = 1; 79 /** Indicates that we need to un-enroll. */ 80 public static final int MANAGE_ACTION_UN_ENROLL = 2; 81 82 //-- Flags for startRecogntion ----// 83 /** Empty flag for {@link #startRecognition(int)}. */ 84 public static final int RECOGNITION_FLAG_NONE = 0; 85 /** 86 * Recognition flag for {@link #startRecognition(int)} that indicates 87 * whether the trigger audio for hotword needs to be captured. 88 */ 89 public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1; 90 91 //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----// 92 // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags. 93 94 /** 95 * Simple recognition of the key phrase. Returned by {@link #getSupportedRecognitionModes()} 96 */ 97 public static final int RECOGNITION_MODE_VOICE_TRIGGER 98 = SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER; 99 /** 100 * Trigger only if one user is identified. Returned by {@link #getSupportedRecognitionModes()} 101 */ 102 public static final int RECOGNITION_MODE_USER_IDENTIFICATION 103 = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; 104 105 static final String TAG = "AlwaysOnHotwordDetector"; 106 // TODO: Set to false. 107 static final boolean DBG = true; 108 109 private static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; 110 private static final int STATUS_OK = SoundTrigger.STATUS_OK; 111 112 private static final int MSG_STATE_CHANGED = 1; 113 private static final int MSG_HOTWORD_DETECTED = 2; 114 private static final int MSG_DETECTION_STARTED = 3; 115 private static final int MSG_DETECTION_STOPPED = 4; 116 private static final int MSG_DETECTION_ERROR = 5; 117 118 private static final int FLAG_REQUESTED = 0x1; 119 private static final int FLAG_STARTED = 0x2; 120 private static final int FLAG_CALL_ACTIVE = 0x4; 121 private static final int FLAG_MICROPHONE_OPEN = 0x8; 122 123 private final String mText; 124 private final String mLocale; 125 /** 126 * The metadata of the Keyphrase, derived from the enrollment application. 127 * This may be null if this keyphrase isn't supported by the enrollment application. 128 */ 129 private final KeyphraseMetadata mKeyphraseMetadata; 130 private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; 131 private final IVoiceInteractionService mVoiceInteractionService; 132 private final IVoiceInteractionManagerService mModelManagementService; 133 private final SoundTriggerListener mInternalCallback; 134 private final Callback mExternalCallback; 135 private final Object mLock = new Object(); 136 private final Handler mHandler; 137 138 private int mAvailability = STATE_NOT_READY; 139 private int mInternalState = 0; 140 private int mRecognitionFlags = RECOGNITION_FLAG_NONE; 141 142 /** 143 * Callbacks for always-on hotword detection. 144 */ 145 public interface Callback { 146 /** 147 * Called when the hotword availability changes. 148 * This indicates a change in the availability of recognition for the given keyphrase. 149 * It's called at least once with the initial availability.<p/> 150 * 151 * Availability implies whether the hardware on this system is capable of listening for 152 * the given keyphrase or not. <p/> 153 * If the return code is one of {@link #STATE_HARDWARE_UNAVAILABLE} or 154 * {@link #STATE_KEYPHRASE_UNSUPPORTED}, 155 * detection is not possible and no further interaction should be 156 * performed with this detector. <br/> 157 * If it is {@link #STATE_KEYPHRASE_UNENROLLED} the caller may choose to begin 158 * an enrollment flow for the keyphrase. <br/> 159 * and for {@link #STATE_KEYPHRASE_ENROLLED} a recognition can be started as desired. <p/> 160 */ 161 void onAvailabilityChanged(int status); 162 /** 163 * Called when the keyphrase is spoken. 164 * This implicitly stops listening for the keyphrase once it's detected. 165 * Clients should start a recognition again once they are done handling this 166 * detection. 167 * 168 * @param data Optional trigger audio data, if it was requested during 169 * {@link AlwaysOnHotwordDetector#startRecognition(int)}. 170 */ 171 void onDetected(byte[] data); 172 /** 173 * Called when the detection for the associated keyphrase starts. 174 * This is called as a result of a successful call to 175 * {@link AlwaysOnHotwordDetector#startRecognition(int)}. 176 */ 177 void onDetectionStarted(); 178 /** 179 * Called when the detection for the associated keyphrase stops. 180 * This is called as a result of a successful call to 181 * {@link AlwaysOnHotwordDetector#stopRecognition()}. 182 */ 183 void onDetectionStopped(); 184 /** 185 * Called when the detection fails due to an error. 186 */ 187 void onError(); 188 } 189 190 /** 191 * @param text The keyphrase text to get the detector for. 192 * @param locale The java locale for the detector. 193 * @param callback A non-null Callback for receiving the recognition events. 194 * @param voiceInteractionService The current voice interaction service. 195 * @param modelManagementService A service that allows management of sound models. 196 * 197 * @hide 198 */ 199 public AlwaysOnHotwordDetector(String text, String locale, Callback callback, 200 KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, 201 IVoiceInteractionService voiceInteractionService, 202 IVoiceInteractionManagerService modelManagementService) { 203 mText = text; 204 mLocale = locale; 205 mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; 206 mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); 207 mExternalCallback = callback; 208 mHandler = new MyHandler(); 209 mInternalCallback = new SoundTriggerListener(mHandler); 210 mVoiceInteractionService = voiceInteractionService; 211 mModelManagementService = modelManagementService; 212 new RefreshAvailabiltyTask().execute(); 213 } 214 215 /** 216 * Gets the recognition modes supported by the associated keyphrase. 217 * 218 * @throws UnsupportedOperationException if the keyphrase itself isn't supported. 219 * Callers should only call this method after a supported state callback on 220 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 221 * @throws IllegalStateException if the detector is in an invalid state. 222 * This may happen if another detector has been instantiated or the 223 * {@link VoiceInteractionService} hosting this detector has been shut down. 224 */ 225 public int getSupportedRecognitionModes() { 226 synchronized (mLock) { 227 return getSupportedRecognitionModesLocked(); 228 } 229 } 230 231 private int getSupportedRecognitionModesLocked() { 232 if (mAvailability == STATE_INVALID) { 233 throw new IllegalStateException( 234 "getSupportedRecognitionModes called on an invalid detector"); 235 } 236 237 // This method only makes sense if we can actually support a recognition. 238 if (mAvailability != STATE_KEYPHRASE_ENROLLED 239 && mAvailability != STATE_KEYPHRASE_UNENROLLED) { 240 throw new UnsupportedOperationException( 241 "Getting supported recognition modes for the keyphrase is not supported"); 242 } 243 244 return mKeyphraseMetadata.recognitionModeFlags; 245 } 246 247 /** 248 * Starts recognition for the associated keyphrase. 249 * 250 * @param recognitionFlags The flags to control the recognition properties. 251 * The allowed flags are {@link #RECOGNITION_FLAG_NONE} and 252 * {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO}. 253 * @throws UnsupportedOperationException if the recognition isn't supported. 254 * Callers should only call this method after a supported state callback on 255 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 256 * @throws IllegalStateException if the detector is in an invalid state. 257 * This may happen if another detector has been instantiated or the 258 * {@link VoiceInteractionService} hosting this detector has been shut down. 259 */ 260 public void startRecognition(int recognitionFlags) { 261 synchronized (mLock) { 262 if (mAvailability == STATE_INVALID) { 263 throw new IllegalStateException("startRecognition called on an invalid detector"); 264 } 265 266 // Check if we can start/stop a recognition. 267 if (mAvailability != STATE_KEYPHRASE_ENROLLED) { 268 throw new UnsupportedOperationException( 269 "Recognition for the given keyphrase is not supported"); 270 } 271 272 mInternalState |= FLAG_REQUESTED; 273 mRecognitionFlags = recognitionFlags; 274 updateRecognitionLocked(); 275 } 276 } 277 278 /** 279 * Stops recognition for the associated keyphrase. 280 * 281 * @throws UnsupportedOperationException if the recognition isn't supported. 282 * Callers should only call this method after a supported state callback on 283 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 284 * @throws IllegalStateException if the detector is in an invalid state. 285 * This may happen if another detector has been instantiated or the 286 * {@link VoiceInteractionService} hosting this detector has been shut down. 287 */ 288 public void stopRecognition() { 289 synchronized (mLock) { 290 if (mAvailability == STATE_INVALID) { 291 throw new IllegalStateException("stopRecognition called on an invalid detector"); 292 } 293 294 // Check if we can start/stop a recognition. 295 if (mAvailability != STATE_KEYPHRASE_ENROLLED) { 296 throw new UnsupportedOperationException( 297 "Recognition for the given keyphrase is not supported"); 298 } 299 300 mInternalState &= ~FLAG_REQUESTED; 301 mRecognitionFlags = RECOGNITION_FLAG_NONE; 302 updateRecognitionLocked(); 303 } 304 } 305 306 /** 307 * Gets an intent to manage the associated keyphrase. 308 * 309 * @param action The manage action that needs to be performed. 310 * One of {@link #MANAGE_ACTION_ENROLL}, {@link #MANAGE_ACTION_RE_ENROLL} or 311 * {@link #MANAGE_ACTION_UN_ENROLL}. 312 * @return An {@link Intent} to manage the given keyphrase. 313 * @throws UnsupportedOperationException if managing they keyphrase isn't supported. 314 * Callers should only call this method after a supported state callback on 315 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 316 * @throws IllegalStateException if the detector is in an invalid state. 317 * This may happen if another detector has been instantiated or the 318 * {@link VoiceInteractionService} hosting this detector has been shut down. 319 */ 320 public Intent getManageIntent(int action) { 321 synchronized (mLock) { 322 return getManageIntentLocked(action); 323 } 324 } 325 326 private Intent getManageIntentLocked(int action) { 327 if (mAvailability == STATE_INVALID) { 328 throw new IllegalStateException("getManageIntent called on an invalid detector"); 329 } 330 331 // This method only makes sense if we can actually support a recognition. 332 if (mAvailability != STATE_KEYPHRASE_ENROLLED 333 && mAvailability != STATE_KEYPHRASE_UNENROLLED) { 334 throw new UnsupportedOperationException( 335 "Managing the given keyphrase is not supported"); 336 } 337 338 if (action != MANAGE_ACTION_ENROLL 339 && action != MANAGE_ACTION_RE_ENROLL 340 && action != MANAGE_ACTION_UN_ENROLL) { 341 throw new IllegalArgumentException("Invalid action specified " + action); 342 } 343 344 return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale); 345 } 346 347 /** 348 * Invalidates this hotword detector so that any future calls to this result 349 * in an IllegalStateException. 350 * 351 * @hide 352 */ 353 void invalidate() { 354 synchronized (mLock) { 355 mAvailability = STATE_INVALID; 356 notifyStateChangedLocked(); 357 } 358 } 359 360 /** 361 * Reloads the sound models from the service. 362 * 363 * @hide 364 */ 365 void onSoundModelsChanged() { 366 synchronized (mLock) { 367 // TODO: This should stop the recognition if it was using an enrolled sound model 368 // that's no longer available. 369 if (mAvailability == STATE_INVALID 370 || mAvailability == STATE_HARDWARE_UNAVAILABLE 371 || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) { 372 Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); 373 return; 374 } 375 376 // Execute a refresh availability task - which should then notify of a change. 377 new RefreshAvailabiltyTask().execute(); 378 } 379 } 380 381 @SuppressWarnings("unused") 382 private void onCallStateChanged(boolean active) { 383 synchronized (mLock) { 384 if (active) { 385 mInternalState |= FLAG_CALL_ACTIVE; 386 } else { 387 mInternalState &= ~FLAG_CALL_ACTIVE; 388 } 389 390 updateRecognitionLocked(); 391 } 392 } 393 394 @SuppressWarnings("unused") 395 private void onMicrophoneStateChanged(boolean open) { 396 synchronized (mLock) { 397 if (open) { 398 mInternalState |= FLAG_MICROPHONE_OPEN; 399 } else { 400 mInternalState &= ~FLAG_MICROPHONE_OPEN; 401 } 402 403 updateRecognitionLocked(); 404 } 405 } 406 407 private void updateRecognitionLocked() { 408 // Don't attempt to update the recognition state if keyphrase isn't enrolled. 409 if (mAvailability != STATE_KEYPHRASE_ENROLLED) { 410 return; 411 } 412 413 // Start recognition if requested and not in a call/reading from the microphone 414 boolean start = (mInternalState&FLAG_REQUESTED) != 0 415 && (mInternalState&FLAG_CALL_ACTIVE) == 0 416 && (mInternalState&FLAG_MICROPHONE_OPEN) == 0; 417 boolean requested = (mInternalState&FLAG_REQUESTED) != 0; 418 419 if (start && (mInternalState&FLAG_STARTED) == 0) { 420 // Start recognition. 421 if (DBG) Slog.d(TAG, "starting recognition..."); 422 int status = startRecognitionLocked(); 423 if (status == STATUS_OK) { 424 mHandler.sendEmptyMessage(MSG_DETECTION_STARTED); 425 } else { 426 if (DBG) Slog.d(TAG, "failed to start recognition: " + status); 427 mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); 428 } 429 // Post the callback 430 return; 431 } 432 433 if (!start && (mInternalState&FLAG_STARTED) != 0) { 434 // Stop recognition 435 // Only notify the callback if a recognition was *not* requested. 436 // For internal stoppages, don't notify the callback. 437 if (DBG) Slog.d(TAG, "stopping recognition..."); 438 int status = stopRecognitionLocked(); 439 if (status == STATUS_OK) { 440 if (!requested) mHandler.sendEmptyMessage(MSG_DETECTION_STOPPED); 441 } else { 442 if (!requested) mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); 443 if (DBG) Slog.d(TAG, "failed to stop recognition: " + status); 444 } 445 return; 446 } 447 } 448 449 private int startRecognitionLocked() { 450 KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1]; 451 // TODO: Do we need to do something about the confidence level here? 452 recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.id, 453 mKeyphraseMetadata.recognitionModeFlags, new ConfidenceLevel[0]); 454 boolean captureTriggerAudio = 455 (mRecognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; 456 int code = STATUS_ERROR; 457 try { 458 code = mModelManagementService.startRecognition(mVoiceInteractionService, 459 mKeyphraseMetadata.id, mInternalCallback, 460 new RecognitionConfig( 461 captureTriggerAudio, recognitionExtra, null /* additional data */)); 462 } catch (RemoteException e) { 463 Slog.w(TAG, "RemoteException in startRecognition!"); 464 } 465 if (code != STATUS_OK) { 466 Slog.w(TAG, "startRecognition() failed with error code " + code); 467 } 468 return code; 469 } 470 471 private int stopRecognitionLocked() { 472 int code = STATUS_ERROR; 473 try { 474 code = mModelManagementService.stopRecognition( 475 mVoiceInteractionService, mKeyphraseMetadata.id, mInternalCallback); 476 } catch (RemoteException e) { 477 Slog.w(TAG, "RemoteException in stopRecognition!"); 478 } 479 480 if (code != STATUS_OK) { 481 Slog.w(TAG, "stopRecognition() failed with error code " + code); 482 } 483 return code; 484 } 485 486 private void notifyStateChangedLocked() { 487 Message message = Message.obtain(mHandler, MSG_STATE_CHANGED); 488 message.arg1 = mAvailability; 489 message.sendToTarget(); 490 } 491 492 /** @hide */ 493 static final class SoundTriggerListener extends IRecognitionStatusCallback.Stub { 494 private final Handler mHandler; 495 496 public SoundTriggerListener(Handler handler) { 497 mHandler = handler; 498 } 499 500 @Override 501 public void onDetected(KeyphraseRecognitionEvent event) { 502 Slog.i(TAG, "onDetected"); 503 Message message = Message.obtain(mHandler, MSG_HOTWORD_DETECTED); 504 message.obj = event.data; 505 message.sendToTarget(); 506 } 507 508 @Override 509 public void onError(int status) { 510 Slog.i(TAG, "onError: " + status); 511 mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); 512 } 513 } 514 515 class MyHandler extends Handler { 516 @Override 517 public void handleMessage(Message msg) { 518 synchronized (mLock) { 519 if (mAvailability == STATE_INVALID) { 520 Slog.w(TAG, "Received message: " + msg.what + " for an invalid detector"); 521 return; 522 } 523 } 524 525 switch (msg.what) { 526 case MSG_STATE_CHANGED: 527 mExternalCallback.onAvailabilityChanged(msg.arg1); 528 break; 529 case MSG_HOTWORD_DETECTED: 530 synchronized (mLock) { 531 mInternalState &= ~FLAG_REQUESTED; 532 mInternalState &= ~FLAG_STARTED; 533 } 534 mExternalCallback.onDetected((byte[]) msg.obj); 535 break; 536 case MSG_DETECTION_STARTED: 537 synchronized (mLock) { 538 mInternalState |= FLAG_STARTED; 539 } 540 mExternalCallback.onDetectionStarted(); 541 break; 542 case MSG_DETECTION_STOPPED: 543 synchronized (mLock) { 544 mInternalState &= ~FLAG_REQUESTED; 545 mInternalState &= ~FLAG_STARTED; 546 } 547 mExternalCallback.onDetectionStopped(); 548 break; 549 case MSG_DETECTION_ERROR: 550 synchronized (mLock) { 551 mInternalState &= ~FLAG_REQUESTED; 552 mInternalState &= ~FLAG_STARTED; 553 } 554 mExternalCallback.onError(); 555 break; 556 default: 557 super.handleMessage(msg); 558 } 559 } 560 } 561 562 class RefreshAvailabiltyTask extends AsyncTask<Void, Void, Void> { 563 564 @Override 565 public Void doInBackground(Void... params) { 566 int availability = internalGetInitialAvailability(); 567 boolean enrolled = false; 568 // Fetch the sound model if the availability is one of the supported ones. 569 if (availability == STATE_NOT_READY 570 || availability == STATE_KEYPHRASE_UNENROLLED 571 || availability == STATE_KEYPHRASE_ENROLLED) { 572 enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id); 573 if (!enrolled) { 574 availability = STATE_KEYPHRASE_UNENROLLED; 575 } else { 576 availability = STATE_KEYPHRASE_ENROLLED; 577 } 578 } 579 580 synchronized (mLock) { 581 if (DBG) { 582 Slog.d(TAG, "Hotword availability changed from " + mAvailability 583 + " -> " + availability); 584 } 585 mAvailability = availability; 586 notifyStateChangedLocked(); 587 } 588 return null; 589 } 590 591 /** 592 * @return The initial availability without checking the enrollment status. 593 */ 594 private int internalGetInitialAvailability() { 595 synchronized (mLock) { 596 // This detector has already been invalidated. 597 if (mAvailability == STATE_INVALID) { 598 return STATE_INVALID; 599 } 600 } 601 602 ModuleProperties dspModuleProperties = null; 603 try { 604 dspModuleProperties = 605 mModelManagementService.getDspModuleProperties(mVoiceInteractionService); 606 } catch (RemoteException e) { 607 Slog.w(TAG, "RemoteException in getDspProperties!"); 608 } 609 // No DSP available 610 if (dspModuleProperties == null) { 611 return STATE_HARDWARE_UNAVAILABLE; 612 } 613 // No enrollment application supports this keyphrase/locale 614 if (mKeyphraseMetadata == null) { 615 return STATE_KEYPHRASE_UNSUPPORTED; 616 } 617 return STATE_NOT_READY; 618 } 619 620 /** 621 * @return The corresponding {@link KeyphraseSoundModel} or null if none is found. 622 */ 623 private boolean internalGetIsEnrolled(int keyphraseId) { 624 try { 625 return mModelManagementService.isEnrolledForKeyphrase( 626 mVoiceInteractionService, keyphraseId); 627 } catch (RemoteException e) { 628 Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!"); 629 } 630 return false; 631 } 632 } 633} 634