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

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

Merge "AlwaysOnHotwordDetector needs to reflect enrollment changes" into lmp-dev

parents caf26263 6daae962
Loading
Loading
Loading
Loading
+8 −5
Original line number Diff line number Diff line
@@ -27324,10 +27324,6 @@ package android.service.voice {
    method public int getSupportedRecognitionModes();
    method public int startRecognition(int);
    method public int stopRecognition();
    field public static final int KEYPHRASE_ENROLLED = 2; // 0x2
    field public static final int KEYPHRASE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
    field public static final int KEYPHRASE_UNENROLLED = 1; // 0x1
    field public static final int KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
    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
@@ -27335,6 +27331,11 @@ package android.service.voice {
    field public static final int RECOGNITION_FLAG_NONE = 0; // 0x0
    field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
    field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
    field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
    field public static final int STATE_INVALID = -3; // 0xfffffffd
    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
  }
@@ -27346,10 +27347,12 @@ package android.service.voice {
  public class VoiceInteractionService extends android.app.Service {
    ctor public VoiceInteractionService();
    method public final android.service.voice.AlwaysOnHotwordDetector getAlwaysOnHotwordDetector(java.lang.String, java.lang.String, android.service.voice.AlwaysOnHotwordDetector.Callback);
    method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(java.lang.String, java.lang.String, android.service.voice.AlwaysOnHotwordDetector.Callback);
    method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
    method public android.os.IBinder onBind(android.content.Intent);
    method public void onReady();
    method public void onShutdown();
    method public void onSoundModelsChanged();
    method public void startSession(android.os.Bundle);
    field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
    field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction";
+85 −37
Original line number Diff line number Diff line
@@ -41,26 +41,32 @@ import java.util.List;
 * always-on keyphrase detection APIs.
 */
public class AlwaysOnHotwordDetector {
    //---- States of Keyphrase availability ----//
    //---- States of Keyphrase availability. Return codes for getAvailability() ----//
    /**
     * Indicates that the given keyphrase is not available on the system because of the
     * hardware configuration.
     * Indicates that this hotword detector is no longer valid for any recognition
     * and should not be used anymore.
     */
    public static final int KEYPHRASE_HARDWARE_UNAVAILABLE = -2;
    public static final int STATE_INVALID = -3;
    /**
     * Indicates that the given keyphrase is not supported.
     * Indicates that recognition for the given keyphrase is not available on the system
     * because of the hardware configuration.
     */
    public static final int KEYPHRASE_UNSUPPORTED = -1;
    public static final int STATE_HARDWARE_UNAVAILABLE = -2;
    /**
     * Indicates that recognition for the given keyphrase is not supported.
     */
    public static final int STATE_KEYPHRASE_UNSUPPORTED = -1;
    /**
     * Indicates that the given keyphrase is not enrolled.
     */
    public static final int KEYPHRASE_UNENROLLED = 1;
    public static final int STATE_KEYPHRASE_UNENROLLED = 1;
    /**
     * Indicates that the given keyphrase is currently enrolled but not being actively listened for.
     * Indicates that the given keyphrase is currently enrolled and it's possible to start
     * recognition for it.
     */
    public static final int KEYPHRASE_ENROLLED = 2;
    public static final int STATE_KEYPHRASE_ENROLLED = 2;

    // Keyphrase management actions ----//
    // Keyphrase management actions. Used in getManageIntent() ----//
    /** Indicates that we need to enroll. */
    public static final int MANAGE_ACTION_ENROLL = 0;
    /** Indicates that we need to re-enroll. */
@@ -83,7 +89,7 @@ public class AlwaysOnHotwordDetector {
     */
    public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1;

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

    /**
@@ -109,17 +115,20 @@ public class AlwaysOnHotwordDetector {
     * This may be null if this keyphrase isn't supported by the enrollment application.
     */
    private final KeyphraseMetadata mKeyphraseMetadata;
    /**
     * The sound model for the keyphrase, derived from the model management service
     * (IVoiceInteractionManagerService). May be null if the keyphrase isn't enrolled yet.
     */
    private final KeyphraseSoundModel mEnrolledSoundModel;
    private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
    private final IVoiceInteractionService mVoiceInteractionService;
    private final IVoiceInteractionManagerService mModelManagementService;
    private final SoundTriggerListener mInternalCallback;
    private final Callback mExternalCallback;
    private final boolean mDisabled;
    private final Object mLock = new Object();

    /**
     * The sound model for the keyphrase, derived from the model management service
     * (IVoiceInteractionManagerService). May be null if the keyphrase isn't enrolled yet.
     */
    private KeyphraseSoundModel mEnrolledSoundModel;
    private boolean mInvalidated;

    /**
     * Callbacks for always-on hotword detection.
@@ -151,6 +160,7 @@ public class AlwaysOnHotwordDetector {
            KeyphraseEnrollmentInfo keyphraseEnrollmentInfo,
            IVoiceInteractionService voiceInteractionService,
            IVoiceInteractionManagerService modelManagementService) {
        mInvalidated = false;
        mText = text;
        mLocale = locale;
        mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo;
@@ -160,28 +170,34 @@ public class AlwaysOnHotwordDetector {
        mVoiceInteractionService = voiceInteractionService;
        mModelManagementService = modelManagementService;
        if (mKeyphraseMetadata != null) {
            mEnrolledSoundModel = internalGetKeyphraseSoundModel(mKeyphraseMetadata.id);
        } else {
            mEnrolledSoundModel = null;
            mEnrolledSoundModel = internalGetKeyphraseSoundModelLocked(mKeyphraseMetadata.id);
        }
        int initialAvailability = internalGetAvailabilityLocked();
        mDisabled = (initialAvailability == KEYPHRASE_HARDWARE_UNAVAILABLE)
                || (initialAvailability == KEYPHRASE_UNSUPPORTED);
        mDisabled = (initialAvailability == STATE_HARDWARE_UNAVAILABLE)
                || (initialAvailability == STATE_KEYPHRASE_UNSUPPORTED);
    }

    /**
     * Gets the state of always-on hotword detection for the given keyphrase and locale
     * on this system.
     * Availability implies that the hardware on this system is capable of listening for
     * the given keyphrase or not.
     * the given keyphrase or not. <p/>
     * If the return code is one of {@link #STATE_HARDWARE_UNAVAILABLE} or
     * {@link #STATE_KEYPHRASE_UNSUPPORTED}, no further interaction should be performed with this
     * detector. <br/>
     * If the state is {@link #STATE_KEYPHRASE_UNENROLLED} the caller may choose to begin
     * an enrollment flow for the keyphrase. <br/>
     * For {@value #STATE_KEYPHRASE_ENROLLED} a recognition can be started as desired. <br/>
     * If the return code is {@link #STATE_INVALID}, this detector is stale and must not be used.
     * A new detector should be obtained and used.
     *
     * @return Indicates if always-on hotword detection is available for the given keyphrase.
     *         The return code is one of {@link #KEYPHRASE_HARDWARE_UNAVAILABLE},
     *         {@link #KEYPHRASE_UNSUPPORTED}, {@link #KEYPHRASE_UNENROLLED} or
     *         {@link #KEYPHRASE_ENROLLED}.
     *         The return code is one of {@link #STATE_HARDWARE_UNAVAILABLE},
     *         {@link #STATE_KEYPHRASE_UNSUPPORTED}, {@link #STATE_KEYPHRASE_UNENROLLED},
     *         {@link #STATE_KEYPHRASE_ENROLLED}, or {@link #STATE_INVALID}.
     */
    public int getAvailability() {
        synchronized (this) {
        synchronized (mLock) {
            return internalGetAvailabilityLocked();
        }
    }
@@ -194,7 +210,7 @@ public class AlwaysOnHotwordDetector {
     *         before calling this method to avoid this exception.
     */
    public int getSupportedRecognitionModes() {
        synchronized (this) {
        synchronized (mLock) {
            return getSupportedRecognitionModesLocked();
        }
    }
@@ -220,13 +236,13 @@ public class AlwaysOnHotwordDetector {
     *         before calling this method to avoid this exception.
     */
    public int startRecognition(int recognitionFlags) {
        synchronized (this) {
        synchronized (mLock) {
            return startRecognitionLocked(recognitionFlags);
        }
    }

    private int startRecognitionLocked(int recognitionFlags) {
        if (internalGetAvailabilityLocked() != KEYPHRASE_ENROLLED) {
        if (internalGetAvailabilityLocked() != STATE_KEYPHRASE_ENROLLED) {
            throw new UnsupportedOperationException(
                    "Recognition for the given keyphrase is not supported");
        }
@@ -261,13 +277,13 @@ public class AlwaysOnHotwordDetector {
     *         before calling this method to avoid this exception.
     */
    public int stopRecognition() {
        synchronized (this) {
        synchronized (mLock) {
            return stopRecognitionLocked();
        }
    }

    private synchronized int stopRecognitionLocked() {
        if (internalGetAvailabilityLocked() != KEYPHRASE_ENROLLED) {
    private int stopRecognitionLocked() {
        if (internalGetAvailabilityLocked() != STATE_KEYPHRASE_ENROLLED) {
            throw new UnsupportedOperationException(
                    "Recognition for the given keyphrase is not supported");
        }
@@ -312,6 +328,10 @@ public class AlwaysOnHotwordDetector {
    }

    private int internalGetAvailabilityLocked() {
        if (mInvalidated) {
            return STATE_INVALID;
        }

        ModuleProperties dspModuleProperties = null;
        try {
            dspModuleProperties =
@@ -321,23 +341,51 @@ public class AlwaysOnHotwordDetector {
        }
        // No DSP available
        if (dspModuleProperties == null) {
            return KEYPHRASE_HARDWARE_UNAVAILABLE;
            return STATE_HARDWARE_UNAVAILABLE;
        }
        // No enrollment application supports this keyphrase/locale
        if (mKeyphraseMetadata == null) {
            return KEYPHRASE_UNSUPPORTED;
            return STATE_KEYPHRASE_UNSUPPORTED;
        }

        // This keyphrase hasn't been enrolled.
        if (mEnrolledSoundModel == null) {
            return KEYPHRASE_UNENROLLED;
            return STATE_KEYPHRASE_UNENROLLED;
        }
        return STATE_KEYPHRASE_ENROLLED;
    }

    /**
     * Invalidates this hotword detector so that any future calls to this result
     * in an IllegalStateException.
     *
     * @hide
     */
    void invalidate() {
        synchronized (mLock) {
            mInvalidated = true;
        }
    }

    /**
     * Reloads the sound models from the service.
     *
     * @hide
     */
    void onSoundModelsChanged() {
        synchronized (mLock) {
            // TODO: This should stop the recognition if it was using an enrolled sound model
            // that's no longer available.
            if (mKeyphraseMetadata != null) {
                mEnrolledSoundModel = internalGetKeyphraseSoundModelLocked(mKeyphraseMetadata.id);
            }
        }
        return KEYPHRASE_ENROLLED;
    }

    /**
     * @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
     */
    private KeyphraseSoundModel internalGetKeyphraseSoundModel(int keyphraseId) {
    private KeyphraseSoundModel internalGetKeyphraseSoundModelLocked(int keyphraseId) {
        List<KeyphraseSoundModel> soundModels;
        try {
            soundModels = mModelManagementService
+2 −0
Original line number Diff line number Diff line
@@ -21,4 +21,6 @@ package android.service.voice;
 */
oneway interface IVoiceInteractionService {
    void ready();
    void soundModelsChanged();
    void shutdown();
}
+87 −9
Original line number Diff line number Diff line
@@ -28,8 +28,8 @@ import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;

import android.provider.Settings;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractionManagerService;

@@ -70,15 +70,27 @@ public class VoiceInteractionService extends Service {
        @Override public void ready() {
            mHandler.sendEmptyMessage(MSG_READY);
        }
        @Override public void shutdown() {
            mHandler.sendEmptyMessage(MSG_SHUTDOWN);
        }
        @Override public void soundModelsChanged() {
            mHandler.sendEmptyMessage(MSG_SOUND_MODELS_CHANGED);
        }
    };

    MyHandler mHandler;

    IVoiceInteractionManagerService mSystemService;

    private final Object mLock = new Object();

    private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;

    private AlwaysOnHotwordDetector mHotwordDetector;

    static final int MSG_READY = 1;
    static final int MSG_SHUTDOWN = 2;
    static final int MSG_SOUND_MODELS_CHANGED = 3;

    class MyHandler extends Handler {
        @Override
@@ -87,6 +99,12 @@ public class VoiceInteractionService extends Service {
                case MSG_READY:
                    onReady();
                    break;
                case MSG_SHUTDOWN:
                    onShutdownInternal();
                    break;
                case MSG_SOUND_MODELS_CHANGED:
                    onSoundModelsChangedInternal();
                    break;
                default:
                    super.handleMessage(msg);
            }
@@ -141,8 +159,9 @@ public class VoiceInteractionService extends Service {
    /**
     * Called during service initialization to tell you when the system is ready
     * to receive interaction from it. You should generally do initialization here
     * rather than in {@link #onCreate()}.  Methods such as {@link #startSession}
     * and {@link #getAlwaysOnHotwordDetector} will not be operational until this point.
     * rather than in {@link #onCreate()}. Methods such as {@link #startSession(Bundle)} and
     * {@link #createAlwaysOnHotwordDetector(String, String, android.service.voice.AlwaysOnHotwordDetector.Callback)}
     * will not be operational until this point.
     */
    public void onReady() {
        mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
@@ -150,23 +169,68 @@ public class VoiceInteractionService extends Service {
        mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
    }

    private void onShutdownInternal() {
        onShutdown();
        // Stop any active recognitions when shutting down.
        // This ensures that if implementations forget to stop any active recognition,
        // It's still guaranteed to have been stopped.
        // This helps with cases where the voice interaction implementation is changed
        // by the user.
        safelyShutdownHotwordDetector();
    }

    /**
     * Called during service de-initialization to tell you when the system is shutting the
     * service down.
     */
    public void onShutdown() {
    }

    private void onSoundModelsChangedInternal() {
        synchronized (this) {
            if (mHotwordDetector != null) {
                // TODO: Stop recognition if a sound model that was being recognized gets deleted.
                mHotwordDetector.onSoundModelsChanged();
            }
        }
        onSoundModelsChanged();
    }

    /**
     * Called when the sound models available for recognition change.
     * This may be called if a new sound model is available or
     * an existing one is updated or removed.
     * Implementations must check the availability of the hotword detector if they own one
     * by calling {@link AlwaysOnHotwordDetector#getAvailability()} before calling into it.
     */
    public void onSoundModelsChanged() {
    }

    /**
     * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
     * This instance must be retained and used by the client.
     * Calling this a second time invalidates the previously created hotword detector
     * which can no longer be used to manage recognition.
     *
     * @param keyphrase The keyphrase that's being used, for example "Hello Android".
     * @param locale The locale for which the enrollment needs to be performed.
     *        This is a Java locale, for example "en_US".
     * @param callback The callback to notify of detection events.
     * @return An always-on hotword detector for the given keyphrase and locale.
     */
    public final AlwaysOnHotwordDetector getAlwaysOnHotwordDetector(
    public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
            String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) {
        if (mSystemService == null) {
            throw new IllegalStateException("Not available until onReady() is called");
        }
        // TODO: Cache instances and return the same one instead of creating a new interactor
        // for the same keyphrase/locale combination.
        return new AlwaysOnHotwordDetector(keyphrase, locale, callback,
        synchronized (mLock) {
            // Allow only one concurrent recognition via the APIs.
            safelyShutdownHotwordDetector();
            mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
                    mKeyphraseEnrollmentInfo, mInterface, mSystemService);
        }
        return mHotwordDetector;
    }

    /**
     * @return Details of keyphrases available for enrollment.
@@ -176,4 +240,18 @@ public class VoiceInteractionService extends Service {
    protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
        return mKeyphraseEnrollmentInfo;
    }

    private void safelyShutdownHotwordDetector() {
        try {
            synchronized (mLock) {
                if (mHotwordDetector != null) {
                    mHotwordDetector.stopRecognition();
                    mHotwordDetector.invalidate();
                    mHotwordDetector = null;
                }
            }
        } catch (Exception ex) {
            // Ignore.
        }
    }
}
+13 −10
Original line number Diff line number Diff line
@@ -290,20 +290,23 @@ public class VoiceInteractionManagerService extends SystemService {

                final long caller = Binder.clearCallingIdentity();
                try {
                    // If the keyphrases are not present in the model, delete the model.
                    boolean success = false;
                    if (model.keyphrases == null) {
                        if (mDbHelper.deleteKeyphraseSoundModel(model.uuid)) {
                            return SoundTriggerHelper.STATUS_OK;
                        // If the keyphrases are not present in the model, delete the model.
                        success = mDbHelper.deleteKeyphraseSoundModel(model.uuid);
                    } else {
                            return SoundTriggerHelper.STATUS_ERROR;
                        // Else update the model.
                        success = mDbHelper.addOrUpdateKeyphraseSoundModel(model);
                    }
                    if (success) {
                        // Notify the voice interaction service of a change in sound models.
                        if (mImpl != null && mImpl.mService != null) {
                            mImpl.notifySoundModelsChangedLocked();
                        }
                    } else {
                        if (mDbHelper.addOrUpdateKeyphraseSoundModel(model)) {
                        return SoundTriggerHelper.STATUS_OK;
                    } else {
                        return SoundTriggerHelper.STATUS_ERROR;
                    }
                    }
                } finally {
                    Binder.restoreCallingIdentity(caller);
                }
Loading