Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c848009 authored by Sandeep Siddhartha's avatar Sandeep Siddhartha Committed by Android (Google) Code Review
Browse files

Merge "Add a flag for multiple triggers with same recognition session" into lmp-dev

parents 83f0f755 2c0273e5
Loading
Loading
Loading
Loading
+9 −5
Original line number Diff line number Diff line
@@ -27493,11 +27493,12 @@ package android.service.voice {
  public class AlwaysOnHotwordDetector {
    method public android.content.Intent getManageIntent(int);
    method public int getSupportedRecognitionModes();
    method public void startRecognition(int);
    method public void stopRecognition();
    method public boolean startRecognition(int);
    method public boolean stopRecognition();
    field public static final int MANAGE_ACTION_ENROLL = 0; // 0x0
    field public static final int MANAGE_ACTION_RE_ENROLL = 1; // 0x1
    field public static final int MANAGE_ACTION_UN_ENROLL = 2; // 0x2
    field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
    field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
    field public static final int RECOGNITION_FLAG_NONE = 0; // 0x0
    field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
@@ -27510,12 +27511,15 @@ package android.service.voice {
  public static abstract interface AlwaysOnHotwordDetector.Callback {
    method public abstract void onAvailabilityChanged(int);
    method public abstract void onDetected(byte[]);
    method public abstract void onDetectionStarted();
    method public abstract void onDetectionStopped();
    method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.TriggerAudio);
    method public abstract void onError();
  }
  public static class AlwaysOnHotwordDetector.TriggerAudio {
    field public final android.media.AudioFormat audioFormat;
    field public final byte[] data;
  }
  public class VoiceInteractionService extends android.app.Service {
    ctor public VoiceInteractionService();
    method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(java.lang.String, java.lang.String, android.service.voice.AlwaysOnHotwordDetector.Callback);
+13 −5
Original line number Diff line number Diff line
@@ -524,6 +524,11 @@ public class SoundTrigger {
        /** True if the DSP should capture the trigger sound and make it available for further
         * capture. */
        public final boolean captureRequested;
        /**
         * True if the service should restart listening after the DSP triggers.
         * Note: This config flag is currently used at the service layer rather than by the DSP.
         */
        public final boolean allowMultipleTriggers;
        /** List of all keyphrases in the sound model for which recognition should be performed with
         * options for each keyphrase. */
        public final KeyphraseRecognitionExtra keyphrases[];
@@ -531,9 +536,10 @@ public class SoundTrigger {
         * typically during enrollment. */
        public final byte[] data;

        public RecognitionConfig(boolean captureRequested,
        public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
                KeyphraseRecognitionExtra keyphrases[], byte[] data) {
            this.captureRequested = captureRequested;
            this.allowMultipleTriggers = allowMultipleTriggers;
            this.keyphrases = keyphrases;
            this.data = data;
        }
@@ -551,15 +557,17 @@ public class SoundTrigger {

        private static RecognitionConfig fromParcel(Parcel in) {
            boolean captureRequested = in.readByte() == 1;
            boolean allowMultipleTriggers = in.readByte() == 1;
            KeyphraseRecognitionExtra[] keyphrases =
                    in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
            byte[] data = in.readBlob();
            return new RecognitionConfig(captureRequested, keyphrases, data);
            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeByte((byte) (captureRequested ? 1 : 0));
            dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
            dest.writeTypedArray(keyphrases, flags);
            dest.writeBlob(data);
        }
@@ -571,9 +579,9 @@ public class SoundTrigger {

        @Override
        public String toString() {
            return "RecognitionConfig [captureRequested=" + captureRequested + ", keyphrases="
                    + Arrays.toString(keyphrases)
                    + ", data=" + (data == null ? 0 : data.length) + "]";
            return "RecognitionConfig [captureRequested=" + captureRequested
                    + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
        }
    }

+100 −136
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package android.service.voice;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
@@ -27,6 +30,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
@@ -35,6 +39,9 @@ import android.util.Slog;

import com.android.internal.app.IVoiceInteractionManagerService;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A class that lets a VoiceInteractionService implementation interact with
 * always-on keyphrase detection APIs.
@@ -82,7 +89,17 @@ public class AlwaysOnHotwordDetector {
    /** Indicates that we need to un-enroll. */
    public static final int MANAGE_ACTION_UN_ENROLL = 2;

    //-- Flags for startRecogntion    ----//
    //-- Flags for startRecognition    ----//
    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true,
            value = {
                RECOGNITION_FLAG_NONE,
                RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
            })
    public @interface RecognitionFlags {}

    /** Empty flag for {@link #startRecognition(int)}. */
    public static final int RECOGNITION_FLAG_NONE = 0;
    /**
@@ -90,10 +107,28 @@ public class AlwaysOnHotwordDetector {
     * whether the trigger audio for hotword needs to be captured.
     */
    public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1;
    /**
     * Recognition flag for {@link #startRecognition(int)} that indicates
     * whether the recognition should keep going on even after the keyphrase triggers.
     * If this flag is specified, it's possible to get multiple triggers after a
     * call to {@link #startRecognition(int)} if the user speaks the keyphrase multiple times.
     * When this isn't specified, the default behavior is to stop recognition once the
     * keyphrase is spoken, till the caller starts recognition again.
     */
    public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;

    //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
    // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true,
            value = {
                RECOGNITION_MODE_VOICE_TRIGGER,
                RECOGNITION_MODE_USER_IDENTIFICATION,
            })
    public @interface RecognitionModes {}

    /**
     * Simple recognition of the key phrase.
     * Returned by {@link #getSupportedRecognitionModes()}
@@ -114,16 +149,9 @@ public class AlwaysOnHotwordDetector {
    private static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
    private static final int STATUS_OK = SoundTrigger.STATUS_OK;

    private static final int MSG_STATE_CHANGED = 1;
    private static final int MSG_AVAILABILITY_CHANGED = 1;
    private static final int MSG_HOTWORD_DETECTED = 2;
    private static final int MSG_DETECTION_STARTED = 3;
    private static final int MSG_DETECTION_STOPPED = 4;
    private static final int MSG_DETECTION_ERROR = 5;

    private static final int FLAG_REQUESTED = 0x1;
    private static final int FLAG_STARTED = 0x2;
    private static final int FLAG_CALL_ACTIVE = 0x4;
    private static final int FLAG_MICROPHONE_OPEN = 0x8;
    private static final int MSG_DETECTION_ERROR = 3;

    private final String mText;
    private final String mLocale;
@@ -141,8 +169,27 @@ public class AlwaysOnHotwordDetector {
    private final Handler mHandler;

    private int mAvailability = STATE_NOT_READY;
    private int mInternalState = 0;
    private int mRecognitionFlags = RECOGNITION_FLAG_NONE;

    /**
     * Details of the audio that triggered the keyphrase.
     */
    public static class TriggerAudio {
        /**
         * Format of {@code data}.
         */
        @NonNull
        public final AudioFormat audioFormat;
        /**
         * Raw audio data that triggered they keyphrase.
         */
        @NonNull
        public final byte[] data;

        private TriggerAudio(AudioFormat _audioFormat, byte[] _data) {
            audioFormat = _audioFormat;
            data = _data;
        }
    }

    /**
     * Callbacks for always-on hotword detection.
@@ -168,22 +215,10 @@ public class AlwaysOnHotwordDetector {
         * Clients should start a recognition again once they are done handling this
         * detection.
         *
         * @param data Optional trigger audio data, if it was requested during
         * @param triggerAudio Optional trigger audio data, if it was requested during
         *        {@link AlwaysOnHotwordDetector#startRecognition(int)}.
         */
        void onDetected(byte[] data);
        /**
         * Called when the detection for the associated keyphrase starts.
         * This is called as a result of a successful call to
         * {@link AlwaysOnHotwordDetector#startRecognition(int)}.
         */
        void onDetectionStarted();
        /**
         * Called when the detection for the associated keyphrase stops.
         * This is called as a result of a successful call to
         * {@link AlwaysOnHotwordDetector#stopRecognition()}.
         */
        void onDetectionStopped();
        void onDetected(@Nullable TriggerAudio triggerAudio);
        /**
         * Called when the detection fails due to an error.
         */
@@ -228,7 +263,8 @@ public class AlwaysOnHotwordDetector {
     *         This may happen if another detector has been instantiated or the
     *         {@link VoiceInteractionService} hosting this detector has been shut down.
     */
    public int getSupportedRecognitionModes() {
    public @RecognitionModes int getSupportedRecognitionModes() {
        if (DBG) Slog.d(TAG, "getSupportedRecognitionModes()");
        synchronized (mLock) {
            return getSupportedRecognitionModesLocked();
        }
@@ -254,8 +290,10 @@ public class AlwaysOnHotwordDetector {
     * Starts recognition for the associated keyphrase.
     *
     * @param recognitionFlags The flags to control the recognition properties.
     *        The allowed flags are {@link #RECOGNITION_FLAG_NONE} and
     *        {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO}.
     *        The allowed flags are {@link #RECOGNITION_FLAG_NONE},
     *        {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO} and
     *        {@link #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS}.
     * @return Indicates whether the call succeeded or not.
     * @throws UnsupportedOperationException if the recognition isn't supported.
     *         Callers should only call this method after a supported state callback on
     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
@@ -263,7 +301,8 @@ public class AlwaysOnHotwordDetector {
     *         This may happen if another detector has been instantiated or the
     *         {@link VoiceInteractionService} hosting this detector has been shut down.
     */
    public void startRecognition(int recognitionFlags) {
    public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
        if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
        synchronized (mLock) {
            if (mAvailability == STATE_INVALID) {
                throw new IllegalStateException("startRecognition called on an invalid detector");
@@ -275,15 +314,14 @@ public class AlwaysOnHotwordDetector {
                        "Recognition for the given keyphrase is not supported");
            }

            mInternalState |= FLAG_REQUESTED;
            mRecognitionFlags = recognitionFlags;
            updateRecognitionLocked();
            return startRecognitionLocked(recognitionFlags) == STATUS_OK;
        }
    }

    /**
     * Stops recognition for the associated keyphrase.
     *
     * @return Indicates whether the call succeeded or not.
     * @throws UnsupportedOperationException if the recognition isn't supported.
     *         Callers should only call this method after a supported state callback on
     *         {@link Callback#onAvailabilityChanged(int)} to avoid this exception.
@@ -291,7 +329,8 @@ public class AlwaysOnHotwordDetector {
     *         This may happen if another detector has been instantiated or the
     *         {@link VoiceInteractionService} hosting this detector has been shut down.
     */
    public void stopRecognition() {
    public boolean stopRecognition() {
        if (DBG) Slog.d(TAG, "stopRecognition()");
        synchronized (mLock) {
            if (mAvailability == STATE_INVALID) {
                throw new IllegalStateException("stopRecognition called on an invalid detector");
@@ -303,9 +342,7 @@ public class AlwaysOnHotwordDetector {
                        "Recognition for the given keyphrase is not supported");
            }

            mInternalState &= ~FLAG_REQUESTED;
            mRecognitionFlags = RECOGNITION_FLAG_NONE;
            updateRecognitionLocked();
            return stopRecognitionLocked() == STATUS_OK;
        }
    }

@@ -324,6 +361,7 @@ public class AlwaysOnHotwordDetector {
     *         {@link VoiceInteractionService} hosting this detector has been shut down.
     */
    public Intent getManageIntent(int action) {
        if (DBG) Slog.d(TAG, "getManageIntent(" + action + ")");
        synchronized (mLock) {
            return getManageIntentLocked(action);
        }
@@ -370,7 +408,7 @@ public class AlwaysOnHotwordDetector {
     */
    void onSoundModelsChanged() {
        synchronized (mLock) {
            // TODO: This should stop the recognition if it was using an enrolled sound model
            // FIXME: This should stop the recognition if it was using an enrolled sound model
            // that's no longer available.
            if (mAvailability == STATE_INVALID
                    || mAvailability == STATE_HARDWARE_UNAVAILABLE
@@ -384,87 +422,21 @@ public class AlwaysOnHotwordDetector {
        }
    }

    @SuppressWarnings("unused")
    private void onCallStateChanged(boolean active) {
        synchronized (mLock) {
            if (active) {
                mInternalState |= FLAG_CALL_ACTIVE;
            } else {
                mInternalState &= ~FLAG_CALL_ACTIVE;
            }

            updateRecognitionLocked();
        }
    }

    @SuppressWarnings("unused")
    private void onMicrophoneStateChanged(boolean open) {
        synchronized (mLock) {
            if (open) {
                mInternalState |= FLAG_MICROPHONE_OPEN;
            } else {
                mInternalState &= ~FLAG_MICROPHONE_OPEN;
            }

            updateRecognitionLocked();
        }
    }

    private void updateRecognitionLocked() {
        // Don't attempt to update the recognition state if keyphrase isn't enrolled.
        if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
            return;
        }

        // Start recognition if requested and not in a call/reading from the microphone
        boolean start = (mInternalState&FLAG_REQUESTED) != 0
                && (mInternalState&FLAG_CALL_ACTIVE) == 0
                && (mInternalState&FLAG_MICROPHONE_OPEN) == 0;
        boolean requested = (mInternalState&FLAG_REQUESTED) != 0;

        if (start && (mInternalState&FLAG_STARTED) == 0) {
            // Start recognition.
            if (DBG) Slog.d(TAG, "starting recognition...");
            int status = startRecognitionLocked();
            if (status == STATUS_OK) {
                mHandler.sendEmptyMessage(MSG_DETECTION_STARTED);
            } else {
                if (DBG) Slog.d(TAG, "failed to start recognition: " + status);
                mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
            }
            // Post the callback
            return;
        }

        if (!start && (mInternalState&FLAG_STARTED) != 0) {
            // Stop recognition
            // Only notify the callback if a recognition was *not* requested.
            // For internal stoppages, don't notify the callback.
            if (DBG) Slog.d(TAG, "stopping recognition...");
            int status = stopRecognitionLocked();
            if (status == STATUS_OK) {
                if (!requested) mHandler.sendEmptyMessage(MSG_DETECTION_STOPPED);
            } else {
                if (!requested) mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
                if (DBG) Slog.d(TAG, "failed to stop recognition: " + status);
            }
            return;
        }
    }

    private int startRecognitionLocked() {
    private int startRecognitionLocked(int recognitionFlags) {
        KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
        // TODO: Do we need to do something about the confidence level here?
        recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.id,
                mKeyphraseMetadata.recognitionModeFlags, new ConfidenceLevel[0]);
        boolean captureTriggerAudio =
                (mRecognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
                (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
        boolean allowMultipleTriggers =
                (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
        int code = STATUS_ERROR;
        try {
            code = mModelManagementService.startRecognition(mVoiceInteractionService,
                    mKeyphraseMetadata.id, mInternalCallback,
                    new RecognitionConfig(
                            captureTriggerAudio, recognitionExtra, null /* additional data */));
                    new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
                            recognitionExtra, null /* additional data */));
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in startRecognition!");
        }
@@ -490,7 +462,7 @@ public class AlwaysOnHotwordDetector {
    }

    private void notifyStateChangedLocked() {
        Message message = Message.obtain(mHandler, MSG_STATE_CHANGED);
        Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
        message.arg1 = mAvailability;
        message.sendToTarget();
    }
@@ -505,9 +477,22 @@ public class AlwaysOnHotwordDetector {

        @Override
        public void onDetected(KeyphraseRecognitionEvent event) {
            if (DBG) {
                Slog.d(TAG, "OnDetected(" + event + ")");
            } else {
                Slog.i(TAG, "onDetected");
            }
            Message message = Message.obtain(mHandler, MSG_HOTWORD_DETECTED);
            message.obj = event.data;
            // FIXME: Check whether the event contains trigger data or not.
            // FIXME: Read the audio format from the event.
            if (event.data != null) {
                AudioFormat audioFormat = new AudioFormat.Builder()
                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                        .setSampleRate(16000)
                        .build();
                message.obj = new TriggerAudio(audioFormat, event.data);
            }
            message.sendToTarget();
        }

@@ -529,34 +514,13 @@ public class AlwaysOnHotwordDetector {
            }

            switch (msg.what) {
                case MSG_STATE_CHANGED:
                case MSG_AVAILABILITY_CHANGED:
                    mExternalCallback.onAvailabilityChanged(msg.arg1);
                    break;
                case MSG_HOTWORD_DETECTED:
                    synchronized (mLock) {
                        mInternalState &= ~FLAG_REQUESTED;
                        mInternalState &= ~FLAG_STARTED;
                    }
                    mExternalCallback.onDetected((byte[]) msg.obj);
                    break;
                case MSG_DETECTION_STARTED:
                    synchronized (mLock) {
                        mInternalState |= FLAG_STARTED;
                    }
                    mExternalCallback.onDetectionStarted();
                    break;
                case MSG_DETECTION_STOPPED:
                    synchronized (mLock) {
                        mInternalState &= ~FLAG_REQUESTED;
                        mInternalState &= ~FLAG_STARTED;
                    }
                    mExternalCallback.onDetectionStopped();
                    mExternalCallback.onDetected((TriggerAudio) msg.obj);
                    break;
                case MSG_DETECTION_ERROR:
                    synchronized (mLock) {
                        mInternalState &= ~FLAG_REQUESTED;
                        mInternalState &= ~FLAG_STARTED;
                    }
                    mExternalCallback.onError();
                    break;
                default:
+16 −2
Original line number Diff line number Diff line
@@ -65,6 +65,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {

    private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
    private UUID mCurrentSoundModelUuid = null;
    // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
    private RecognitionConfig mRecognitionConfig = null;

    SoundTriggerHelper() {
        ArrayList <ModuleProperties> modules = new ArrayList<>();
@@ -174,6 +176,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
        // Everything went well!
        mCurrentSoundModelHandle = soundModelHandle;
        mCurrentSoundModelUuid = soundModel.uuid;
        mRecognitionConfig = recognitionConfig;
        // Register the new listener. This replaces the old one.
        // There can only be a maximum of one active listener for a keyphrase
        // at any given time.
@@ -221,12 +224,12 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
            int status = mModule.stopRecognition(mCurrentSoundModelHandle);
            if (status != SoundTrigger.STATUS_OK) {
                Slog.w(TAG, "stopRecognition call failed with " + status);
                return STATUS_ERROR;
                return status;
            }
            status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
            if (status != SoundTrigger.STATUS_OK) {
                Slog.w(TAG, "unloadSoundModel call failed with " + status);
                return STATUS_ERROR;
                return status;
            }

            mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
@@ -284,6 +287,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
                            Slog.w(TAG, "received onRecognition event without any listener for it");
                            return;
                        }

                        // FIXME: Remove this block if the lower layer supports multiple triggers.
                        if (mRecognitionConfig != null
                                && mRecognitionConfig.allowMultipleTriggers) {
                            int status = mModule.startRecognition(
                                    mCurrentSoundModelHandle, mRecognitionConfig);
                            if (status != STATUS_OK) {
                                Slog.w(TAG, "Error in restarting recognition after a trigger");
                                listener.onError(status);
                            }
                        }
                    }
                } catch (RemoteException e) {
                    Slog.w(TAG, "RemoteException in onDetectionStopped");
+8 −13
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.AlwaysOnHotwordDetector.Callback;
import android.service.voice.AlwaysOnHotwordDetector.TriggerAudio;
import android.service.voice.VoiceInteractionService;
import android.util.Log;

@@ -36,20 +37,10 @@ public class MainInteractionService extends VoiceInteractionService {
        }

        @Override
        public void onDetected(byte[] data) {
        public void onDetected(TriggerAudio triggerAudio) {
            Log.i(TAG, "onDetected");
        }

        @Override
        public void onDetectionStarted() {
            Log.i(TAG, "onDetectionStarted");
        }

        @Override
        public void onDetectionStopped() {
            Log.i(TAG, "onDetectionStopped");
        }

        @Override
        public void onError() {
            Log.i(TAG, "onError");
@@ -95,8 +86,12 @@ public class MainInteractionService extends VoiceInteractionService {
                break;
            case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:
                Log.i(TAG, "STATE_KEYPHRASE_ENROLLED - starting recognition");
                mHotwordDetector.startRecognition(
                        AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE);
                if (mHotwordDetector.startRecognition(
                        AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE)) {
                    Log.i(TAG, "startRecognition succeeded");
                } else {
                    Log.i(TAG, "startRecognition failed");
                }
                break;
        }
    }