summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSandeep Siddhartha <sansid@google.com>2014-08-05 15:03:46 -0700
committerSandeep Siddhartha <sansid@google.com>2014-08-07 11:53:02 -0700
commitcb4e81c7fe1ec843d80f7604a688c71086c23685 (patch)
treec3ea65e45d02993cd7c3af55222c5cda5ec08998
parent3da5ba05d601778ea11dd87c1e8c9e9827e2a520 (diff)
downloadframeworks_base-cb4e81c7fe1ec843d80f7604a688c71086c23685.zip
frameworks_base-cb4e81c7fe1ec843d80f7604a688c71086c23685.tar.gz
frameworks_base-cb4e81c7fe1ec843d80f7604a688c71086c23685.tar.bz2
Handle microphone contention/Phone calls while recognition is active
Internally we pause the recognition when: - a phone call is active/off-hook/ringing - or some other application grabs the microphone we auto-resume when the condition that caused us to pause reverses. Both these events are notified to the client via callbacks so that they can choose to display on their UI, that the recognition is paused for some reason. Bug: 16515468 Bug: 16740806 Bug: 16514535 Change-Id: Ib274d68522c8cf37d42402c875b16159957657f0
-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;