AlwaysOnHotwordDetector.java revision fd7070fdff6864abf750beaef697cc2fdb8c5eb9
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 triggerAvailable; 192 /** 193 * Indicates if {@code captureSession} can be used to continue capturing more audio from 194 * the DSP hardware. 195 * 196 * Candidate for public API 197 * @hide 198 */ 199 public final boolean captureAvailable; 200 /** 201 * The session to use when attempting to capture more audio from the DSP hardware. 202 * 203 * Candidate for public API 204 * TODO: When unhiding, change javadoc of audioFormat to - 205 * "Format of {@code data} or the audio that may be captured using {@code captureSession}. 206 * May be null if {@code triggerAvailable} and {@code captureAvailable} are false." 207 * @hide 208 */ 209 public final int captureSession; 210 /** 211 * Format of {@code data}. 212 * May be null if {@code triggerAvailable} is false. 213 */ 214 @Nullable 215 public final AudioFormat audioFormat; 216 /** 217 * Raw data associated with the event. 218 * This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true. 219 */ 220 @Nullable 221 public final byte[] data; 222 223 private EventPayload(boolean _triggerAvailable, boolean _captureAvailable, 224 AudioFormat _audioFormat, int _captureSession, byte[] _data) { 225 triggerAvailable = _triggerAvailable; 226 captureAvailable = _captureAvailable; 227 captureSession = _captureSession; 228 audioFormat = _audioFormat; 229 data = _data; 230 } 231 } 232 233 /** 234 * Callbacks for always-on hotword detection. 235 */ 236 public interface Callback { 237 /** 238 * Called when the hotword availability changes. 239 * This indicates a change in the availability of recognition for the given keyphrase. 240 * It's called at least once with the initial availability.<p/> 241 * 242 * Availability implies whether the hardware on this system is capable of listening for 243 * the given keyphrase or not. <p/> 244 * 245 * @see AlwaysOnHotwordDetector#STATE_HARDWARE_UNAVAILABLE 246 * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_UNSUPPORTED 247 * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_UNENROLLED 248 * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_ENROLLED 249 */ 250 void onAvailabilityChanged(int status); 251 /** 252 * Called when the keyphrase is spoken. 253 * This implicitly stops listening for the keyphrase once it's detected. 254 * Clients should start a recognition again once they are done handling this 255 * detection. 256 * 257 * @param eventPayload Payload data for the detection event. 258 * This may contain the trigger audio, if requested when calling 259 * {@link AlwaysOnHotwordDetector#startRecognition(int)}. 260 */ 261 void onDetected(@NonNull EventPayload eventPayload); 262 /** 263 * Called when the detection fails due to an error. 264 */ 265 void onError(); 266 /** 267 * Called when the recognition is paused temporarily for some reason. 268 * This is an informational callback, and the clients shouldn't be doing anything here 269 * except showing an indication on their UI if they have to. 270 */ 271 void onRecognitionPaused(); 272 /** 273 * Called when the recognition is resumed after it was temporarily paused. 274 * This is an informational callback, and the clients shouldn't be doing anything here 275 * except showing an indication on their UI if they have to. 276 */ 277 void onRecognitionResumed(); 278 } 279 280 /** 281 * @param text The keyphrase text to get the detector for. 282 * @param locale The java locale for the detector. 283 * @param callback A non-null Callback for receiving the recognition events. 284 * @param voiceInteractionService The current voice interaction service. 285 * @param modelManagementService A service that allows management of sound models. 286 * 287 * @hide 288 */ 289 public AlwaysOnHotwordDetector(String text, String locale, Callback callback, 290 KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, 291 IVoiceInteractionService voiceInteractionService, 292 IVoiceInteractionManagerService modelManagementService) { 293 mText = text; 294 mLocale = locale; 295 mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; 296 mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); 297 mExternalCallback = callback; 298 mHandler = new MyHandler(); 299 mInternalCallback = new SoundTriggerListener(mHandler); 300 mVoiceInteractionService = voiceInteractionService; 301 mModelManagementService = modelManagementService; 302 new RefreshAvailabiltyTask().execute(); 303 } 304 305 /** 306 * Gets the recognition modes supported by the associated keyphrase. 307 * 308 * @see #RECOGNITION_MODE_USER_IDENTIFICATION 309 * @see #RECOGNITION_MODE_VOICE_TRIGGER 310 * 311 * @throws UnsupportedOperationException if the keyphrase itself isn't supported. 312 * Callers should only call this method after a supported state callback on 313 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 314 * @throws IllegalStateException if the detector is in an invalid state. 315 * This may happen if another detector has been instantiated or the 316 * {@link VoiceInteractionService} hosting this detector has been shut down. 317 */ 318 public @RecognitionModes int getSupportedRecognitionModes() { 319 if (DBG) Slog.d(TAG, "getSupportedRecognitionModes()"); 320 synchronized (mLock) { 321 return getSupportedRecognitionModesLocked(); 322 } 323 } 324 325 private int getSupportedRecognitionModesLocked() { 326 if (mAvailability == STATE_INVALID) { 327 throw new IllegalStateException( 328 "getSupportedRecognitionModes 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 "Getting supported recognition modes for the keyphrase is not supported"); 336 } 337 338 return mKeyphraseMetadata.recognitionModeFlags; 339 } 340 341 /** 342 * Starts recognition for the associated keyphrase. 343 * 344 * @param recognitionFlags The flags to control the recognition properties. 345 * The allowed flags are {@link #RECOGNITION_FLAG_NONE}, 346 * {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO} and 347 * {@link #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS}. 348 * @return Indicates whether the call succeeded or not. 349 * @throws UnsupportedOperationException if the recognition isn't supported. 350 * Callers should only call this method after a supported state callback on 351 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 352 * @throws IllegalStateException if the detector is in an invalid state. 353 * This may happen if another detector has been instantiated or the 354 * {@link VoiceInteractionService} hosting this detector has been shut down. 355 */ 356 public boolean startRecognition(@RecognitionFlags int recognitionFlags) { 357 if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); 358 synchronized (mLock) { 359 if (mAvailability == STATE_INVALID) { 360 throw new IllegalStateException("startRecognition called on an invalid detector"); 361 } 362 363 // Check if we can start/stop a recognition. 364 if (mAvailability != STATE_KEYPHRASE_ENROLLED) { 365 throw new UnsupportedOperationException( 366 "Recognition for the given keyphrase is not supported"); 367 } 368 369 return startRecognitionLocked(recognitionFlags) == STATUS_OK; 370 } 371 } 372 373 /** 374 * Stops recognition for the associated keyphrase. 375 * 376 * @return Indicates whether the call succeeded or not. 377 * @throws UnsupportedOperationException if the recognition isn't supported. 378 * Callers should only call this method after a supported state callback on 379 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 380 * @throws IllegalStateException if the detector is in an invalid state. 381 * This may happen if another detector has been instantiated or the 382 * {@link VoiceInteractionService} hosting this detector has been shut down. 383 */ 384 public boolean stopRecognition() { 385 if (DBG) Slog.d(TAG, "stopRecognition()"); 386 synchronized (mLock) { 387 if (mAvailability == STATE_INVALID) { 388 throw new IllegalStateException("stopRecognition called on an invalid detector"); 389 } 390 391 // Check if we can start/stop a recognition. 392 if (mAvailability != STATE_KEYPHRASE_ENROLLED) { 393 throw new UnsupportedOperationException( 394 "Recognition for the given keyphrase is not supported"); 395 } 396 397 return stopRecognitionLocked() == STATUS_OK; 398 } 399 } 400 401 /** 402 * Gets an intent to manage the associated keyphrase. 403 * 404 * @param action The manage action that needs to be performed. 405 * One of {@link #MANAGE_ACTION_ENROLL}, {@link #MANAGE_ACTION_RE_ENROLL} or 406 * {@link #MANAGE_ACTION_UN_ENROLL}. 407 * @return An {@link Intent} to manage the given keyphrase. 408 * @throws UnsupportedOperationException if managing they keyphrase isn't supported. 409 * Callers should only call this method after a supported state callback on 410 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 411 * @throws IllegalStateException if the detector is in an invalid state. 412 * This may happen if another detector has been instantiated or the 413 * {@link VoiceInteractionService} hosting this detector has been shut down. 414 */ 415 public Intent getManageIntent(@ManageActions int action) { 416 if (DBG) Slog.d(TAG, "getManageIntent(" + action + ")"); 417 synchronized (mLock) { 418 return getManageIntentLocked(action); 419 } 420 } 421 422 private Intent getManageIntentLocked(int action) { 423 if (mAvailability == STATE_INVALID) { 424 throw new IllegalStateException("getManageIntent called on an invalid detector"); 425 } 426 427 // This method only makes sense if we can actually support a recognition. 428 if (mAvailability != STATE_KEYPHRASE_ENROLLED 429 && mAvailability != STATE_KEYPHRASE_UNENROLLED) { 430 throw new UnsupportedOperationException( 431 "Managing the given keyphrase is not supported"); 432 } 433 434 if (action != MANAGE_ACTION_ENROLL 435 && action != MANAGE_ACTION_RE_ENROLL 436 && action != MANAGE_ACTION_UN_ENROLL) { 437 throw new IllegalArgumentException("Invalid action specified " + action); 438 } 439 440 return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale); 441 } 442 443 /** 444 * Invalidates this hotword detector so that any future calls to this result 445 * in an IllegalStateException. 446 * 447 * @hide 448 */ 449 void invalidate() { 450 synchronized (mLock) { 451 mAvailability = STATE_INVALID; 452 notifyStateChangedLocked(); 453 } 454 } 455 456 /** 457 * Reloads the sound models from the service. 458 * 459 * @hide 460 */ 461 void onSoundModelsChanged() { 462 synchronized (mLock) { 463 // FIXME: This should stop the recognition if it was using an enrolled sound model 464 // that's no longer available. 465 if (mAvailability == STATE_INVALID 466 || mAvailability == STATE_HARDWARE_UNAVAILABLE 467 || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) { 468 Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); 469 return; 470 } 471 472 // Execute a refresh availability task - which should then notify of a change. 473 new RefreshAvailabiltyTask().execute(); 474 } 475 } 476 477 private int startRecognitionLocked(int recognitionFlags) { 478 KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1]; 479 // TODO: Do we need to do something about the confidence level here? 480 recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.id, 481 mKeyphraseMetadata.recognitionModeFlags, 0, new ConfidenceLevel[0]); 482 boolean captureTriggerAudio = 483 (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; 484 boolean allowMultipleTriggers = 485 (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; 486 int code = STATUS_ERROR; 487 try { 488 code = mModelManagementService.startRecognition(mVoiceInteractionService, 489 mKeyphraseMetadata.id, mInternalCallback, 490 new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, 491 recognitionExtra, null /* additional data */)); 492 } catch (RemoteException e) { 493 Slog.w(TAG, "RemoteException in startRecognition!"); 494 } 495 if (code != STATUS_OK) { 496 Slog.w(TAG, "startRecognition() failed with error code " + code); 497 } 498 return code; 499 } 500 501 private int stopRecognitionLocked() { 502 int code = STATUS_ERROR; 503 try { 504 code = mModelManagementService.stopRecognition( 505 mVoiceInteractionService, mKeyphraseMetadata.id, mInternalCallback); 506 } catch (RemoteException e) { 507 Slog.w(TAG, "RemoteException in stopRecognition!"); 508 } 509 510 if (code != STATUS_OK) { 511 Slog.w(TAG, "stopRecognition() failed with error code " + code); 512 } 513 return code; 514 } 515 516 private void notifyStateChangedLocked() { 517 Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); 518 message.arg1 = mAvailability; 519 message.sendToTarget(); 520 } 521 522 /** @hide */ 523 static final class SoundTriggerListener extends IRecognitionStatusCallback.Stub { 524 private final Handler mHandler; 525 526 public SoundTriggerListener(Handler handler) { 527 mHandler = handler; 528 } 529 530 @Override 531 public void onDetected(KeyphraseRecognitionEvent event) { 532 if (DBG) { 533 Slog.d(TAG, "onDetected(" + event + ")"); 534 } else { 535 Slog.i(TAG, "onDetected"); 536 } 537 Message.obtain(mHandler, MSG_HOTWORD_DETECTED, 538 new EventPayload(event.triggerInData, event.captureAvailable, 539 event.captureFormat, event.captureSession, event.data)) 540 .sendToTarget(); 541 } 542 543 @Override 544 public void onError(int status) { 545 Slog.i(TAG, "onError: " + status); 546 mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); 547 } 548 549 @Override 550 public void onRecognitionPaused() { 551 Slog.i(TAG, "onRecognitionPaused"); 552 mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE); 553 } 554 555 @Override 556 public void onRecognitionResumed() { 557 Slog.i(TAG, "onRecognitionResumed"); 558 mHandler.sendEmptyMessage(MSG_DETECTION_RESUME); 559 } 560 } 561 562 class MyHandler extends Handler { 563 @Override 564 public void handleMessage(Message msg) { 565 synchronized (mLock) { 566 if (mAvailability == STATE_INVALID) { 567 Slog.w(TAG, "Received message: " + msg.what + " for an invalid detector"); 568 return; 569 } 570 } 571 572 switch (msg.what) { 573 case MSG_AVAILABILITY_CHANGED: 574 mExternalCallback.onAvailabilityChanged(msg.arg1); 575 break; 576 case MSG_HOTWORD_DETECTED: 577 mExternalCallback.onDetected((EventPayload) msg.obj); 578 break; 579 case MSG_DETECTION_ERROR: 580 mExternalCallback.onError(); 581 break; 582 case MSG_DETECTION_PAUSE: 583 mExternalCallback.onRecognitionPaused(); 584 break; 585 case MSG_DETECTION_RESUME: 586 mExternalCallback.onRecognitionResumed(); 587 break; 588 default: 589 super.handleMessage(msg); 590 } 591 } 592 } 593 594 class RefreshAvailabiltyTask extends AsyncTask<Void, Void, Void> { 595 596 @Override 597 public Void doInBackground(Void... params) { 598 int availability = internalGetInitialAvailability(); 599 boolean enrolled = false; 600 // Fetch the sound model if the availability is one of the supported ones. 601 if (availability == STATE_NOT_READY 602 || availability == STATE_KEYPHRASE_UNENROLLED 603 || availability == STATE_KEYPHRASE_ENROLLED) { 604 enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id); 605 if (!enrolled) { 606 availability = STATE_KEYPHRASE_UNENROLLED; 607 } else { 608 availability = STATE_KEYPHRASE_ENROLLED; 609 } 610 } 611 612 synchronized (mLock) { 613 if (DBG) { 614 Slog.d(TAG, "Hotword availability changed from " + mAvailability 615 + " -> " + availability); 616 } 617 mAvailability = availability; 618 notifyStateChangedLocked(); 619 } 620 return null; 621 } 622 623 /** 624 * @return The initial availability without checking the enrollment status. 625 */ 626 private int internalGetInitialAvailability() { 627 synchronized (mLock) { 628 // This detector has already been invalidated. 629 if (mAvailability == STATE_INVALID) { 630 return STATE_INVALID; 631 } 632 } 633 634 ModuleProperties dspModuleProperties = null; 635 try { 636 dspModuleProperties = 637 mModelManagementService.getDspModuleProperties(mVoiceInteractionService); 638 } catch (RemoteException e) { 639 Slog.w(TAG, "RemoteException in getDspProperties!"); 640 } 641 // No DSP available 642 if (dspModuleProperties == null) { 643 return STATE_HARDWARE_UNAVAILABLE; 644 } 645 // No enrollment application supports this keyphrase/locale 646 if (mKeyphraseMetadata == null) { 647 return STATE_KEYPHRASE_UNSUPPORTED; 648 } 649 return STATE_NOT_READY; 650 } 651 652 /** 653 * @return The corresponding {@link KeyphraseSoundModel} or null if none is found. 654 */ 655 private boolean internalGetIsEnrolled(int keyphraseId) { 656 try { 657 return mModelManagementService.isEnrolledForKeyphrase( 658 mVoiceInteractionService, keyphraseId); 659 } catch (RemoteException e) { 660 Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!"); 661 } 662 return false; 663 } 664 } 665} 666