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

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

Merge "Make startRecognition async" into lmp-dev

parents 73915cf2 1ed12ddb
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -27467,8 +27467,8 @@ package android.service.voice {
  public class AlwaysOnHotwordDetector {
    method public android.content.Intent getManageIntent(int);
    method public int getSupportedRecognitionModes();
    method public int startRecognition(int);
    method public int stopRecognition();
    method public void startRecognition(int);
    method public void 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
@@ -27481,14 +27481,14 @@ package android.service.voice {
    field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
    field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
    field public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
    field public static final int STATUS_ERROR = -2147483648; // 0x80000000
    field public static final int STATUS_OK = 0; // 0x0
  }
  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 onError();
  }
  public class VoiceInteractionService extends android.app.Service {
+4 −2
Original line number Diff line number Diff line
@@ -29,7 +29,9 @@ oneway interface IRecognitionStatusCallback {
     */
    void onDetected(in SoundTrigger.KeyphraseRecognitionEvent recognitionEvent);
    /**
     * Called when the detection for the associated keyphrase stops.
     * Called when the detection fails due to an error.
     *
     * @param status The error code that was seen.
     */
    void onDetectionStopped();
    void onError(int status);
}
 No newline at end of file
+163 −67
Original line number Diff line number Diff line
@@ -22,13 +22,11 @@ import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
import android.hardware.soundtrigger.KeyphraseMetadata;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
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.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
@@ -37,8 +35,6 @@ import android.util.Slog;

import com.android.internal.app.IVoiceInteractionManagerService;

import java.util.List;

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

    /**
     * Return codes for {@link #startRecognition(int)}, {@link #stopRecognition()}
     */
    public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
    public static final int STATUS_OK = SoundTrigger.STATUS_OK;

    //-- Flags for startRecogntion    ----//
    /** Empty flag for {@link #startRecognition(int)}. */
    public static final int RECOGNITION_FLAG_NONE = 0;
@@ -115,9 +105,19 @@ public class AlwaysOnHotwordDetector {
    // TODO: Set to false.
    static final boolean DBG = true;

    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_HOTWORD_DETECTED = 2;
    private static final int MSG_DETECTION_STOPPED = 3;
    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 final String mText;
    private final String mLocale;
@@ -135,6 +135,8 @@ public class AlwaysOnHotwordDetector {
    private final Handler mHandler;

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

    /**
     * Callbacks for always-on hotword detection.
@@ -161,15 +163,30 @@ public class AlwaysOnHotwordDetector {
        void onAvailabilityChanged(int status);
        /**
         * Called when the keyphrase is spoken.
         * This implicitly stops listening for the keyphrase once it's detected.
         * Clients should start a recognition again once they are done handling this
         * detection.
         *
         * @param data 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();
        /**
         * Called when the detection fails due to an error.
         */
        void onError();
    }

    /**
@@ -227,78 +244,43 @@ public class AlwaysOnHotwordDetector {
     * @param recognitionFlags The flags to control the recognition properties.
     *        The allowed flags are {@link #RECOGNITION_FLAG_NONE} and
     *        {@link #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO}.
     * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise.
     * @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.
     */
    public int startRecognition(int recognitionFlags) {
    public void startRecognition(int recognitionFlags) {
        synchronized (mLock) {
            return startRecognitionLocked(recognitionFlags);
        }
    }

    private int startRecognitionLocked(int recognitionFlags) {
        // This method only makes sense if we can start a recognition.
            // Check if we can start/stop a recognition.
            if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
                throw new UnsupportedOperationException(
                        "Recognition for the given keyphrase is not supported");
            }

        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 =
                (recognitionFlags & RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
        int code = STATUS_ERROR;
        try {
            code = mModelManagementService.startRecognition(mVoiceInteractionService,
                    mKeyphraseMetadata.id, mInternalCallback,
                    new RecognitionConfig(
                            captureTriggerAudio, recognitionExtra, null /* additional data */));
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in startRecognition!");
            mInternalState |= FLAG_REQUESTED;
            mRecognitionFlags = recognitionFlags;
            updateRecognitionLocked();
        }
        if (code != STATUS_OK) {
            Slog.w(TAG, "startRecognition() failed with error code " + code);
        }
        return code;
    }

    /**
     * Stops recognition for the associated keyphrase.
     *
     * @return {@link #STATUS_OK} if the call succeeds, an error code otherwise.
     * @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.
     */
    public int stopRecognition() {
    public void stopRecognition() {
        synchronized (mLock) {
            return stopRecognitionLocked();
        }
    }

    private int stopRecognitionLocked() {
        // This method only makes sense if we can start a recognition.
            // Check if we can start/stop a recognition.
            if (mAvailability != STATE_KEYPHRASE_ENROLLED) {
                throw new UnsupportedOperationException(
                        "Recognition for the given keyphrase is not supported");
            }

        int code = STATUS_ERROR;
        try {
            code = mModelManagementService.stopRecognition(
                    mVoiceInteractionService, mKeyphraseMetadata.id, mInternalCallback);
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in stopRecognition!");
        }

        if (code != STATUS_OK) {
            Slog.w(TAG, "stopRecognition() failed with error code " + code);
            mInternalState &= ~FLAG_REQUESTED;
            mRecognitionFlags = RECOGNITION_FLAG_NONE;
            updateRecognitionLocked();
        }
        return code;
    }

    /**
@@ -362,6 +344,113 @@ 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) {
                mInternalState |= FLAG_STARTED;
                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) {
                mInternalState &= ~FLAG_STARTED;
                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() {
        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;
        int code = STATUS_ERROR;
        try {
            code = mModelManagementService.startRecognition(mVoiceInteractionService,
                    mKeyphraseMetadata.id, mInternalCallback,
                    new RecognitionConfig(
                            captureTriggerAudio, recognitionExtra, null /* additional data */));
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in startRecognition!");
        }
        if (code != STATUS_OK) {
            Slog.w(TAG, "startRecognition() failed with error code " + code);
        }
        return code;
    }

    private int stopRecognitionLocked() {
        int code = STATUS_ERROR;
        try {
            code = mModelManagementService.stopRecognition(
                    mVoiceInteractionService, mKeyphraseMetadata.id, mInternalCallback);
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException in stopRecognition!");
        }

        if (code != STATUS_OK) {
            Slog.w(TAG, "stopRecognition() failed with error code " + code);
        }
        return code;
    }

    private void notifyStateChangedLocked() {
        Message message = Message.obtain(mHandler, MSG_STATE_CHANGED);
        message.arg1 = mAvailability;
@@ -385,9 +474,9 @@ public class AlwaysOnHotwordDetector {
        }

        @Override
        public void onDetectionStopped() {
            Slog.i(TAG, "onDetectionStopped");
            mHandler.sendEmptyMessage(MSG_DETECTION_STOPPED);
        public void onError(int status) {
            Slog.i(TAG, "onError: " + status);
            mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
        }
    }

@@ -401,8 +490,15 @@ public class AlwaysOnHotwordDetector {
                case MSG_HOTWORD_DETECTED:
                    mExternalCallback.onDetected((byte[]) msg.obj);
                    break;
                case MSG_DETECTION_STARTED:
                    mExternalCallback.onDetectionStarted();
                    break;
                case MSG_DETECTION_STOPPED:
                    mExternalCallback.onDetectionStopped();
                    break;
                case MSG_DETECTION_ERROR:
                    mExternalCallback.onError();
                    break;
                default:
                    super.handleMessage(msg);
            }
+3 −3
Original line number Diff line number Diff line
@@ -129,7 +129,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
        if (oldListener != null && oldListener.asBinder() != listener.asBinder()) {
            Slog.w(TAG, "Canceling previous recognition");
            try {
                oldListener.onDetectionStopped();
                oldListener.onError(STATUS_ERROR);
            } catch (RemoteException e) {
                Slog.w(TAG, "RemoteException in onDetectionStopped");
            }
@@ -235,7 +235,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
                try {
                    synchronized (this) {
                        for (int i = 0; i < mActiveListeners.size(); i++) {
                            mActiveListeners.valueAt(i).onDetectionStopped();
                            mActiveListeners.valueAt(i).onError(STATUS_ERROR);
                        }
                    }
                } catch (RemoteException e) {
@@ -279,7 +279,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
        synchronized (this) {
            try {
                for (int i = 0; i < mActiveListeners.size(); i++) {
                    mActiveListeners.valueAt(i).onDetectionStopped();
                    mActiveListeners.valueAt(i).onError(SoundTrigger.STATUS_DEAD_OBJECT);
                }
            } catch (RemoteException e) {
                Slog.w(TAG, "RemoteException in onDetectionStopped");
+12 −3
Original line number Diff line number Diff line
@@ -40,10 +40,20 @@ public class MainInteractionService extends VoiceInteractionService {
            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");
        }
    };

    private AlwaysOnHotwordDetector mHotwordDetector;
@@ -89,10 +99,9 @@ public class MainInteractionService extends VoiceInteractionService {
                Log.i(TAG, "Need to enroll with " + enroll);
                break;
            case AlwaysOnHotwordDetector.STATE_KEYPHRASE_ENROLLED:
                Log.i(TAG, "STATE_KEYPHRASE_ENROLLED");
                int status = mHotwordDetector.startRecognition(
                Log.i(TAG, "STATE_KEYPHRASE_ENROLLED - starting recognition");
                mHotwordDetector.startRecognition(
                        AlwaysOnHotwordDetector.RECOGNITION_FLAG_NONE);
                Log.i(TAG, "startRecognition status = " + status);
                break;
        }
    }