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