summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt2
-rw-r--r--core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl8
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java32
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java558
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java4
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java10
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;