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