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