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