diff options
6 files changed, 406 insertions, 208 deletions
diff --git a/api/current.txt b/api/current.txt index 1a66712..93360c1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27535,6 +27535,8 @@ package android.service.voice { method public abstract void onAvailabilityChanged(int); method public abstract void onDetected(android.service.voice.AlwaysOnHotwordDetector.EventPayload); method public abstract void onError(); + method public abstract void onRecognitionPaused(); + method public abstract void onRecognitionResumed(); } public static class AlwaysOnHotwordDetector.EventPayload { diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl index f279668..2f6dbe7 100644 --- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl +++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl @@ -34,4 +34,12 @@ oneway interface IRecognitionStatusCallback { * @param status The error code that was seen. */ void onError(int status); + /** + * Called when the recognition is paused temporarily for some reason. + */ + void onRecognitionPaused(); + /** + * Called when the recognition is resumed after it was temporarily paused. + */ + void onRecognitionResumed(); }
\ No newline at end of file diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 9b0643a..4860b44 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -161,6 +161,8 @@ public class AlwaysOnHotwordDetector { private static final int MSG_AVAILABILITY_CHANGED = 1; private static final int MSG_HOTWORD_DETECTED = 2; private static final int MSG_DETECTION_ERROR = 3; + private static final int MSG_DETECTION_PAUSE = 4; + private static final int MSG_DETECTION_RESUME = 5; private final String mText; private final String mLocale; @@ -239,6 +241,18 @@ public class AlwaysOnHotwordDetector { * Called when the detection fails due to an error. */ void onError(); + /** + * Called when the recognition is paused temporarily for some reason. + * This is an informational callback, and the clients shouldn't be doing anything here + * except showing an indication on their UI if they have to. + */ + void onRecognitionPaused(); + /** + * Called when the recognition is resumed after it was temporarily paused. + * This is an informational callback, and the clients shouldn't be doing anything here + * except showing an indication on their UI if they have to. + */ + void onRecognitionResumed(); } /** @@ -508,6 +522,18 @@ public class AlwaysOnHotwordDetector { Slog.i(TAG, "onError: " + status); mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); } + + @Override + public void onRecognitionPaused() { + Slog.i(TAG, "onRecognitionPaused"); + mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE); + } + + @Override + public void onRecognitionResumed() { + Slog.i(TAG, "onRecognitionResumed"); + mHandler.sendEmptyMessage(MSG_DETECTION_RESUME); + } } class MyHandler extends Handler { @@ -530,6 +556,12 @@ public class AlwaysOnHotwordDetector { case MSG_DETECTION_ERROR: mExternalCallback.onError(); break; + case MSG_DETECTION_PAUSE: + mExternalCallback.onRecognitionPaused(); + break; + case MSG_DETECTION_RESUME: + mExternalCallback.onRecognitionResumed(); + break; default: super.handleMessage(msg); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java index fd36bfc..906a0ce 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java @@ -28,8 +28,9 @@ import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; import android.hardware.soundtrigger.SoundTriggerModule; import android.os.RemoteException; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; import android.util.Slog; -import android.util.SparseArray; import java.util.ArrayList; import java.util.UUID; @@ -53,26 +54,37 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; public static final int STATUS_OK = SoundTrigger.STATUS_OK; - private static final int INVALID_SOUND_MODEL_HANDLE = -1; + private static final int INVALID_VALUE = Integer.MIN_VALUE; - /** The {@link DspInfo} for the system, or null if none exists. */ + /** The {@link ModuleProperties} for the system, or null if none exists. */ final ModuleProperties moduleProperties; /** The properties for the DSP module */ private final SoundTriggerModule mModule; - - // Use a RemoteCallbackList here? - private final SparseArray<IRecognitionStatusCallback> mActiveListeners; - - private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; + private final Object mLock = new Object(); + private final TelephonyManager mTelephonyManager; + private final PhoneStateListener mPhoneStateListener; + + // TODO: Since many layers currently only deal with one recognition + // we simplify things by assuming one listener here too. + private IRecognitionStatusCallback mActiveListener; + private int mKeyphraseId = INVALID_VALUE; + private int mCurrentSoundModelHandle = INVALID_VALUE; private UUID mCurrentSoundModelUuid = null; // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer. private RecognitionConfig mRecognitionConfig = null; - - SoundTriggerHelper() { + private boolean mRequested = false; + private boolean mCallActive = false; + // Indicates if the native sound trigger service is disabled or not. + // This is an indirect indication of the microphone being open in some other application. + private boolean mServiceDisabled = false; + private boolean mStarted = false; + + SoundTriggerHelper(TelephonyManager telephonyManager) { ArrayList <ModuleProperties> modules = new ArrayList<>(); int status = SoundTrigger.listModules(modules); - mActiveListeners = new SparseArray<>(1); + mTelephonyManager = telephonyManager; + mPhoneStateListener = new MyCallStateListener(); if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size()); moduleProperties = null; @@ -85,17 +97,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } /** - * @return True, if a recognition for the given {@link Keyphrase} is active. - */ - synchronized boolean isKeyphraseActive(Keyphrase keyphrase) { - if (keyphrase == null) { - Slog.w(TAG, "isKeyphraseActive requires a non-null keyphrase"); - return false; - } - return mActiveListeners.get(keyphrase.id) != null; - } - - /** * Starts recognition for the given keyphraseId. * * @param keyphraseId The identifier of the keyphrase for which @@ -104,85 +105,94 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * @param listener The listener for the recognition events related to the given keyphrase. * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ - synchronized int startRecognition(int keyphraseId, + int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { - if (DBG) { - Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId - + " soundModel=" + soundModel + ", listener=" + listener - + ", recognitionConfig=" + recognitionConfig); - Slog.d(TAG, "moduleProperties=" + moduleProperties); - Slog.d(TAG, "# of current listeners=" + mActiveListeners.size()); - Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle); - Slog.d(TAG, "current SoundModel UUID=" - + (mCurrentSoundModelUuid == null ? null : mCurrentSoundModelUuid)); - } - if (moduleProperties == null || mModule == null) { - Slog.w(TAG, "Attempting startRecognition without the capability"); + if (soundModel == null || listener == null || recognitionConfig == null) { return STATUS_ERROR; } - if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE - && !soundModel.uuid.equals(mCurrentSoundModelUuid)) { - Slog.w(TAG, "Unloading previous sound model"); - int status = mModule.unloadSoundModel(mCurrentSoundModelHandle); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "unloadSoundModel call failed with " + status); - return status; + synchronized (mLock) { + if (DBG) { + Slog.d(TAG, "startRecognition for keyphraseId=" + keyphraseId + + " soundModel=" + soundModel + ", listener=" + listener.asBinder() + + ", recognitionConfig=" + recognitionConfig); + Slog.d(TAG, "moduleProperties=" + moduleProperties); + Slog.d(TAG, "current listener=" + + (mActiveListener == null ? "null" : mActiveListener.asBinder())); + Slog.d(TAG, "current SoundModel handle=" + mCurrentSoundModelHandle); + Slog.d(TAG, "current SoundModel UUID=" + + (mCurrentSoundModelUuid == null ? null : mCurrentSoundModelUuid)); } - mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; - mCurrentSoundModelUuid = null; - } - // If the previous recognition was by a different listener, - // Notify them that it was stopped. - IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId); - if (oldListener != null && oldListener.asBinder() != listener.asBinder()) { - Slog.w(TAG, "Canceling previous recognition"); - try { - oldListener.onError(STATUS_ERROR); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in onDetectionStopped"); + if (!mStarted) { + // Get the current call state synchronously for the first recognition. + mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE; + // Register for call state changes when the first call to start recognition occurs. + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } - mActiveListeners.remove(keyphraseId); - } - // Load the sound model if the current one is null. - int soundModelHandle = mCurrentSoundModelHandle; - if (mCurrentSoundModelHandle == INVALID_SOUND_MODEL_HANDLE - || mCurrentSoundModelUuid == null) { - int[] handle = new int[] { INVALID_SOUND_MODEL_HANDLE }; - int status = mModule.loadSoundModel(soundModel, handle); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "loadSoundModel call failed with " + status); - return status; - } - if (handle[0] == INVALID_SOUND_MODEL_HANDLE) { - Slog.w(TAG, "loadSoundModel call returned invalid sound model handle"); + if (moduleProperties == null || mModule == null) { + Slog.w(TAG, "Attempting startRecognition without the capability"); return STATUS_ERROR; } - soundModelHandle = handle[0]; - } else { - if (DBG) Slog.d(TAG, "Reusing previously loaded sound model"); - } - // Start the recognition. - int status = mModule.startRecognition(soundModelHandle, recognitionConfig); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "startRecognition failed with " + status); - return status; - } + if (mCurrentSoundModelHandle != INVALID_VALUE + && !soundModel.uuid.equals(mCurrentSoundModelUuid)) { + Slog.w(TAG, "Unloading previous sound model"); + int status = mModule.unloadSoundModel(mCurrentSoundModelHandle); + if (status != SoundTrigger.STATUS_OK) { + Slog.w(TAG, "unloadSoundModel call failed with " + status); + } + mCurrentSoundModelHandle = INVALID_VALUE; + mCurrentSoundModelUuid = null; + mStarted = false; + } + + // If the previous recognition was by a different listener, + // Notify them that it was stopped. + if (mActiveListener != null && mActiveListener.asBinder() != listener.asBinder()) { + Slog.w(TAG, "Canceling previous recognition"); + try { + mActiveListener.onError(STATUS_ERROR); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onDetectionStopped"); + } + mActiveListener = null; + } + + // Load the sound model if the current one is null. + int soundModelHandle = mCurrentSoundModelHandle; + if (mCurrentSoundModelHandle == INVALID_VALUE + || mCurrentSoundModelUuid == null) { + int[] handle = new int[] { INVALID_VALUE }; + int status = mModule.loadSoundModel(soundModel, handle); + if (status != SoundTrigger.STATUS_OK) { + Slog.w(TAG, "loadSoundModel call failed with " + status); + return status; + } + if (handle[0] == INVALID_VALUE) { + Slog.w(TAG, "loadSoundModel call returned invalid sound model handle"); + return STATUS_ERROR; + } + soundModelHandle = handle[0]; + } else { + if (DBG) Slog.d(TAG, "Reusing previously loaded sound model"); + } - // 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. - mActiveListeners.put(keyphraseId, listener); - return STATUS_OK; + // Start the recognition. + mRequested = true; + mKeyphraseId = keyphraseId; + 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 at any given time. + mActiveListener = listener; + + return updateRecognitionLocked(false /* don't notify for synchronous calls */); + } } /** @@ -195,173 +205,307 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}. */ - synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { - if (DBG) { - Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId - + ", listener=" + listener); - Slog.d(TAG, "# of current listeners = " + mActiveListeners.size()); - } - - if (moduleProperties == null || mModule == null) { - Slog.w(TAG, "Attempting stopRecognition without the capability"); + int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { + if (listener == null) { return STATUS_ERROR; } - IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId); - if (listener == null) { - Slog.w(TAG, "Attempting stopRecognition without a valid listener"); - return STATUS_ERROR; - } if (currentListener == null) { - // startRecognition hasn't been called or it failed. - Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); - return STATUS_ERROR; - } else if (currentListener.asBinder() != listener.asBinder()) { - // We don't allow a different listener to stop the recognition than the one - // that started it. - Slog.w(TAG, "Attempting stopRecognition for another recognition"); - return STATUS_ERROR; - } else { + synchronized (mLock) { + if (DBG) { + Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + + ", listener=" + listener.asBinder()); + Slog.d(TAG, "current listener=" + + (mActiveListener == null ? "null" : mActiveListener.asBinder())); + } + + if (moduleProperties == null || mModule == null) { + Slog.w(TAG, "Attempting stopRecognition without the capability"); + return STATUS_ERROR; + } + + if (mActiveListener == null) { + // startRecognition hasn't been called or it failed. + Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition"); + return STATUS_ERROR; + } + if (mActiveListener.asBinder() != listener.asBinder()) { + // We don't allow a different listener to stop the recognition than the one + // that started it. + Slog.w(TAG, "Attempting stopRecognition for another recognition"); + return STATUS_ERROR; + } + // Stop recognition if it's the current one, ignore otherwise. - int status = mModule.stopRecognition(mCurrentSoundModelHandle); + mRequested = false; + int status = updateRecognitionLocked(false /* don't notify for synchronous calls */); if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "stopRecognition call failed with " + status); return status; } + status = mModule.unloadSoundModel(mCurrentSoundModelHandle); if (status != SoundTrigger.STATUS_OK) { Slog.w(TAG, "unloadSoundModel call failed with " + status); - return status; } - mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; - mCurrentSoundModelUuid = null; - - mActiveListeners.remove(keyphraseId); - return STATUS_OK; + // Clear the internal state once the recognition has been stopped. + // Unload sound model call may fail in scenarios, and we'd still want + // to reload the sound model. + internalClearStateLocked(); + return status; } } - synchronized void stopAllRecognitions() { - if (moduleProperties == null || mModule == null) { - return; - } - - if (mCurrentSoundModelHandle == INVALID_SOUND_MODEL_HANDLE) { - return; - } + /** + * Stops all recognitions active currently and clears the internal state. + */ + void stopAllRecognitions() { + synchronized (mLock) { + if (moduleProperties == null || mModule == null) { + return; + } - int status = mModule.stopRecognition(mCurrentSoundModelHandle); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "stopRecognition call failed with " + status); - } - status = mModule.unloadSoundModel(mCurrentSoundModelHandle); - if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "unloadSoundModel call failed with " + status); - } + if (mCurrentSoundModelHandle == INVALID_VALUE) { + return; + } - mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; - mCurrentSoundModelUuid = null; + mRequested = false; + int status = updateRecognitionLocked(false /* don't notify for synchronous calls */); + status = mModule.unloadSoundModel(mCurrentSoundModelHandle); + if (status != SoundTrigger.STATUS_OK) { + Slog.w(TAG, "unloadSoundModel call failed with " + status); + } - mActiveListeners.clear(); + internalClearStateLocked(); + } } //---- SoundTrigger.StatusListener methods @Override public void onRecognition(RecognitionEvent event) { - if (event == null) { + if (event == null || !(event instanceof KeyphraseRecognitionEvent)) { Slog.w(TAG, "Invalid recognition event!"); return; } if (DBG) Slog.d(TAG, "onRecognition: " + event); - switch (event.status) { - // Fire aborts/failures to all listeners since it's not tied to a keyphrase. - case SoundTrigger.RECOGNITION_STATUS_ABORT: // fall-through - case SoundTrigger.RECOGNITION_STATUS_FAILURE: - try { - synchronized (this) { - for (int i = 0; i < mActiveListeners.size(); i++) { - mActiveListeners.valueAt(i).onError(STATUS_ERROR); - } - } - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in onDetectionStopped"); - } - break; - case SoundTrigger.RECOGNITION_STATUS_SUCCESS: - if (!(event instanceof KeyphraseRecognitionEvent)) { - Slog.w(TAG, "Invalid recognition event!"); - return; - } - - KeyphraseRecognitionExtra[] keyphraseExtras = - ((KeyphraseRecognitionEvent) event).keyphraseExtras; - if (keyphraseExtras == null || keyphraseExtras.length == 0) { - Slog.w(TAG, "Invalid keyphrase recognition event!"); - return; - } - // TODO: Handle more than one keyphrase extras. - int keyphraseId = keyphraseExtras[0].id; - try { - synchronized(this) { - // Check which keyphrase triggered, and fire the appropriate event. - IRecognitionStatusCallback listener = mActiveListeners.get(keyphraseId); - if (listener != null) { - listener.onDetected((KeyphraseRecognitionEvent) event); - } else { - 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"); - } - break; + synchronized (mLock) { + if (mActiveListener == null) { + Slog.w(TAG, "received onRecognition event without any listener for it"); + return; + } + switch (event.status) { + // Fire aborts/failures to all listeners since it's not tied to a keyphrase. + case SoundTrigger.RECOGNITION_STATUS_ABORT: + onRecognitionAbortLocked(); + break; + case SoundTrigger.RECOGNITION_STATUS_FAILURE: + onRecognitionFailureLocked(); + break; + case SoundTrigger.RECOGNITION_STATUS_SUCCESS: + onRecognitionSuccessLocked((KeyphraseRecognitionEvent) event); + break; + } } } + @Override public void onSoundModelUpdate(SoundModelEvent event) { if (event == null) { Slog.w(TAG, "Invalid sound model event!"); return; } - if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event); - - //TODO: implement sound model update + synchronized (mLock) { + onSoundModelUpdatedLocked(event); + } } + @Override public void onServiceStateChange(int state) { if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state); - - //TODO: implement service state update + synchronized (mLock) { + onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state); + } } @Override public void onServiceDied() { - synchronized (this) { - try { - for (int i = 0; i < mActiveListeners.size(); i++) { - mActiveListeners.valueAt(i).onError(SoundTrigger.STATUS_DEAD_OBJECT); + Slog.e(TAG, "onServiceDied!!"); + synchronized (mLock) { + onServiceDiedLocked(); + } + } + + class MyCallStateListener extends PhoneStateListener { + @Override + public void onCallStateChanged(int state, String arg1) { + if (DBG) Slog.d(TAG, "onCallStateChanged: " + state); + synchronized (mLock) { + onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state); + } + } + } + + private void onCallStateChangedLocked(boolean callActive) { + if (mCallActive == callActive) { + // We consider multiple call states as being active + // so we check if something really changed or not here. + return; + } + mCallActive = callActive; + updateRecognitionLocked(true /* notify */); + } + + private void onSoundModelUpdatedLocked(SoundModelEvent event) { + // TODO: Handle sound model update here. + } + + private void onServiceStateChangedLocked(boolean disabled) { + if (disabled == mServiceDisabled) { + return; + } + mServiceDisabled = disabled; + updateRecognitionLocked(true /* notify */); + } + + private void onRecognitionAbortLocked() { + Slog.w(TAG, "Recognition aborted"); + // No-op + // This is handled via service state changes instead. + } + + private void onRecognitionFailureLocked() { + Slog.w(TAG, "Recognition failure"); + try { + if (mActiveListener != null) { + mActiveListener.onError(STATUS_ERROR); + } + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onError", e); + } finally { + internalClearStateLocked(); + } + } + + private void onRecognitionSuccessLocked(KeyphraseRecognitionEvent event) { + Slog.i(TAG, "Recognition success"); + KeyphraseRecognitionExtra[] keyphraseExtras = + ((KeyphraseRecognitionEvent) event).keyphraseExtras; + if (keyphraseExtras == null || keyphraseExtras.length == 0) { + Slog.w(TAG, "Invalid keyphrase recognition event!"); + return; + } + // TODO: Handle more than one keyphrase extras. + if (mKeyphraseId != keyphraseExtras[0].id) { + Slog.w(TAG, "received onRecognition event for a different keyphrase"); + return; + } + + try { + if (mActiveListener != null) { + mActiveListener.onDetected((KeyphraseRecognitionEvent) event); + } + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onDetected", e); + } + + mStarted = false; + mRequested = mRecognitionConfig.allowMultipleTriggers; + // TODO: Remove this block if the lower layer supports multiple triggers. + if (mRequested) { + updateRecognitionLocked(true /* notify */); + } + } + + private void onServiceDiedLocked() { + try { + if (mActiveListener != null) { + mActiveListener.onError(SoundTrigger.STATUS_DEAD_OBJECT); + } + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onError", e); + } finally { + internalClearStateLocked(); + } + } + + private int updateRecognitionLocked(boolean notify) { + if (mModule == null || moduleProperties == null + || mCurrentSoundModelHandle == INVALID_VALUE || mActiveListener == null) { + // Nothing to do here. + return STATUS_OK; + } + + boolean start = mRequested && !mCallActive && !mServiceDisabled; + if (start == mStarted) { + // No-op. + return STATUS_OK; + } + + // See if the recognition needs to be started. + if (start) { + // Start recognition. + int status = mModule.startRecognition(mCurrentSoundModelHandle, mRecognitionConfig); + if (status != SoundTrigger.STATUS_OK) { + Slog.w(TAG, "startRecognition failed with " + status); + // Notify of error if needed. + if (notify) { + try { + mActiveListener.onError(status); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onError", e); + } + } + } else { + mStarted = true; + // Notify of resume if needed. + if (notify) { + try { + mActiveListener.onRecognitionResumed(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onRecognitionResumed", e); + } + } + } + return status; + } else { + // Stop recognition. + int status = mModule.stopRecognition(mCurrentSoundModelHandle); + if (status != SoundTrigger.STATUS_OK) { + Slog.w(TAG, "stopRecognition call failed with " + status); + if (notify) { + try { + mActiveListener.onError(status); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onError", e); + } + } + } else { + mStarted = false; + // Notify of pause if needed. + if (notify) { + try { + mActiveListener.onRecognitionPaused(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException in onRecognitionPaused", e); + } } - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in onDetectionStopped"); } - mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE; - mCurrentSoundModelUuid = null; - // Remove all listeners. - mActiveListeners.clear(); + return status; } } + + private void internalClearStateLocked() { + mStarted = false; + mRequested = false; + + mKeyphraseId = INVALID_VALUE; + mCurrentSoundModelHandle = INVALID_VALUE; + mCurrentSoundModelUuid = null; + mRecognitionConfig = null; + mActiveListener = null; + + // Unregister from call state changes. + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index eda7bd2..d4489d2 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -38,6 +38,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; +import android.telephony.TelephonyManager; import android.util.Slog; import com.android.internal.app.IVoiceInteractionManagerService; @@ -67,7 +68,8 @@ public class VoiceInteractionManagerService extends SystemService { mContext = context; mResolver = context.getContentResolver(); mDbHelper = new DatabaseHelper(context); - mSoundTriggerHelper = new SoundTriggerHelper(); + mSoundTriggerHelper = new SoundTriggerHelper( + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)); } @Override diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index fcc4626..77c0c32 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -45,6 +45,16 @@ public class MainInteractionService extends VoiceInteractionService { public void onError() { Log.i(TAG, "onError"); } + + @Override + public void onRecognitionPaused() { + Log.i(TAG, "onRecognitionPaused"); + } + + @Override + public void onRecognitionResumed() { + Log.i(TAG, "onRecognitionResumed"); + } }; private AlwaysOnHotwordDetector mHotwordDetector; |