Merge "SoundTriggerHelper re-design." into nyc-dev
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 40687b0..f13e019 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -41,6 +41,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -56,7 +57,6 @@
* (ii) Generic sound-trigger models: Supports multiple of these.
*
* Currently this just acts as an abstraction over all SoundTrigger API calls.
- *
* @hide
*/
public class SoundTriggerHelper implements SoundTrigger.StatusListener {
@@ -84,25 +84,23 @@
private final PhoneStateListener mPhoneStateListener;
private final PowerManager mPowerManager;
- // TODO: Since the voice layer currently only handles one recognition
- // we simplify things by assuming one listener here too.
- private IRecognitionStatusCallback mKeyphraseListener;
-
// The SoundTriggerManager layer handles multiple generic recognition models. We store the
// ModelData here in a hashmap.
private final HashMap<UUID, ModelData> mGenericModelDataMap;
- // Note: KeyphraseId is not really used.
+ // This ModelData instance ensures that the keyphrase sound model is a singleton and
+ // all other sound models are of type Generic. Any keyphrase sound model will be stored here
+ // and any previously running instances will be replaced. This restriction was earlier
+ // implemented by three instance variables which stored data about the keyphrase
+ // model. That data now gets encapsulated in this ModelData instance.
+ private ModelData mKeyphraseModelData;
+
+ // The keyphrase ID for keyphrase sound models. We store this specially here since ModelData
+ // does not support this.
+ // TODO: The role of the keyphrase ID is a bit unclear. Its just used to ensure that
+ // recognition events have the correct keyphrase ID check.
private int mKeyphraseId = INVALID_VALUE;
- // Current voice sound model handle. We only allow one voice model to run at any given time.
- private int mCurrentKeyphraseModelHandle = INVALID_VALUE;
- private KeyphraseSoundModel mCurrentSoundModel = null;
- // FIXME: Ideally this should not be stored if allowMultipleTriggers happens at a lower layer.
- private RecognitionConfig mRecognitionConfig = null;
-
- // Whether we are requesting recognition to start.
- private boolean mRequested = false;
private boolean mCallActive = false;
private boolean mIsPowerSaveMode = false;
// Indicates if the native sound trigger service is disabled or not.
@@ -112,8 +110,6 @@
// Whether we have ANY recognition (keyphrase or generic) running.
private boolean mRecognitionRunning = false;
- // Keeps track of whether the keyphrase recognition is running.
- private boolean mKeyphraseStarted = false;
private boolean mRecognitionAborted = false;
private PowerSaveModeListener mPowerSaveModeListener;
@@ -136,26 +132,89 @@
}
/**
- * Starts recognition for the given generic sound model ID.
+ * Starts recognition for the given generic sound model ID. This is a wrapper around {@link
+ * startRecognition()}.
*
- * @param soundModel The sound model to use for recognition.
- * @param listener The listener for the recognition events related to the given keyphrase.
+ * @param modelId UUID of the sound model.
+ * @param soundModel The generic sound model to use for recognition.
+ * @param callback Callack for the recognition events related to the given keyphrase.
+ * @param recognitionConfig Instance of RecognitionConfig containing the parameters for the
+ * recognition.
* @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
*/
int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
- if (soundModel == null || callback == null || recognitionConfig == null) {
+ MetricsLogger.count(mContext, "sth_start_recognition", 1);
+ if (modelId == null || soundModel == null || callback == null ||
+ recognitionConfig == null) {
Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
return STATUS_ERROR;
}
synchronized (mLock) {
+ ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
+ return startRecognition(soundModel, modelData, callback, recognitionConfig,
+ INVALID_VALUE /* keyphraseId */);
+ }
+ }
+ /**
+ * Starts recognition for the given keyphraseId.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * the recognition is to be started.
+ * @param soundModel The sound model to use for recognition.
+ * @param callback The callback for the recognition events related to the given keyphrase.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
+ IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
+ synchronized (mLock) {
+ MetricsLogger.count(mContext, "sth_start_recognition", 1);
+ if (soundModel == null || callback == null || recognitionConfig == null) {
+ return STATUS_ERROR;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
+ + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
+ + ", recognitionConfig=" + recognitionConfig);
+ Slog.d(TAG, "moduleProperties=" + mModuleProperties);
+ if (mKeyphraseModelData != null) {
+ Slog.d(TAG, mKeyphraseModelData.toString());
+ } else {
+ Slog.d(TAG, "Null KeyphraseModelData.");
+ }
+ }
+ if (mKeyphraseModelData == null) {
+ mKeyphraseModelData = ModelData.createKeyphraseModelData(soundModel.uuid);
+ }
+ return startRecognition(soundModel, mKeyphraseModelData, callback, recognitionConfig,
+ keyphraseId);
+ }
+ }
+
+ /**
+ * Starts recognition for the given sound model. A single routine for both keyphrase and
+ * generic sound models.
+ *
+ * @param soundModel The sound model to use for recognition.
+ * @param modelData Instance of {@link #ModelData} for the given model.
+ * @param callback Callback for the recognition events related to the given keyphrase.
+ * @param recognitionConfig Instance of {@link RecognitionConfig} containing the parameters
+ * @param keyphraseId Keyphrase ID for keyphrase models only. Pass in INVALID_VALUE for other
+ * models.
+ * for the recognition.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ int startRecognition(SoundModel soundModel, ModelData modelData,
+ IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
+ int keyphraseId) {
+ synchronized (mLock) {
if (mModuleProperties == null) {
Slog.w(TAG, "Attempting startRecognition without the capability");
return STATUS_ERROR;
}
-
if (mModule == null) {
mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
if (mModule == null) {
@@ -169,13 +228,43 @@
initializeTelephonyAndPowerStateListeners();
}
- // Fetch a ModelData instance from the hash map. Creates a new one if none
- // exists.
- ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
+ // If the previous model is different (for the same UUID), ensure that its unloaded
+ // and stopped before proceeding. This works for both keyphrase and generic models.
+ // Specifically for keyphrase since we have 'mKeyphraseModelData' holding a single
+ // allowed instance of such a model, this ensures that a previously loaded (or started)
+ // keyphrase model is appropriately stopped. This ensures no regression with the
+ // previous version of this code as given in the startKeyphrase() routine.
+ //
+ // For generic sound models, all this means is that if we are given a different sound
+ // model with the same UUID, then we will "replace" it.
+ if (modelData.getSoundModel() != null) {
+ boolean stopModel = false; // Stop the model after checking that its started.
+ boolean unloadModel = false;
+ if (modelData.getSoundModel().equals(soundModel) && modelData.isModelStarted()) {
+ // The model has not changed, but the previous model is "started".
+ // Stop the previously running model.
+ stopModel = true;
+ unloadModel = false; // No need to unload if the model hasn't changed.
+ } else if (!modelData.getSoundModel().equals(soundModel)) {
+ // We have a different model for this UUID. Stop and unload if needed. This
+ // helps maintain the singleton restriction for keyphrase sound models.
+ stopModel = modelData.isModelStarted();
+ unloadModel = modelData.isModelLoaded();
+ }
+ if (stopModel || unloadModel) {
+ int status = tryStopAndUnloadLocked(modelData, stopModel, unloadModel);
+ if (status != STATUS_OK) {
+ Slog.w(TAG, "Unable to stop or unload previous model: " +
+ modelData.toString());
+ return status;
+ }
+ }
+ }
IRecognitionStatusCallback oldCallback = modelData.getCallback();
- if (oldCallback != null) {
- Slog.w(TAG, "Canceling previous recognition for model id: " + modelId);
+ if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
+ Slog.w(TAG, "Canceling previous recognition for model id: " +
+ modelData.getModelId());
try {
oldCallback.onError(STATUS_ERROR);
} catch (RemoteException e) {
@@ -199,182 +288,49 @@
}
modelData.setHandle(handle[0]);
modelData.setLoaded();
- Slog.d(TAG, "Generic sound model loaded with handle:" + handle[0]);
+ Slog.d(TAG, "Sound model loaded with handle:" + handle[0]);
}
modelData.setCallback(callback);
+ if (modelData.isKeyphraseModel()) {
+ mKeyphraseId = keyphraseId;
+ }
+ modelData.setRequested(true);
modelData.setRecognitionConfig(recognitionConfig);
+ modelData.setSoundModel(soundModel);
- // Don't notify for synchronous calls.
- return startGenericRecognitionLocked(modelData, false);
+ return startRecognitionLocked(modelData,
+ false /* Don't notify for synchronous calls */);
}
}
/**
- * Starts recognition for the given keyphraseId.
- *
- * @param keyphraseId The identifier of the keyphrase for which
- * the recognition is to be started.
- * @param soundModel The sound model to use for recognition.
- * @param listener The listener for the recognition events related to the given keyphrase.
- * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
- */
- int startKeyphraseRecognition(int keyphraseId,
- KeyphraseSoundModel soundModel,
- IRecognitionStatusCallback listener,
- RecognitionConfig recognitionConfig) {
- if (soundModel == null || listener == null || recognitionConfig == null) {
- return STATUS_ERROR;
- }
-
- synchronized (mLock) {
- if (DBG) {
- Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
- + " soundModel=" + soundModel + ", listener=" + listener.asBinder()
- + ", recognitionConfig=" + recognitionConfig);
- Slog.d(TAG, "moduleProperties=" + mModuleProperties);
- Slog.d(TAG, "current listener="
- + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
- Slog.d(TAG, "current SoundModel handle=" + mCurrentKeyphraseModelHandle);
- Slog.d(TAG, "current SoundModel UUID="
- + (mCurrentSoundModel == null ? null : mCurrentSoundModel.uuid));
- }
-
- if (!mRecognitionRunning) {
- initializeTelephonyAndPowerStateListeners();
- }
-
- if (mModuleProperties == null) {
- Slog.w(TAG, "Attempting startKeyphraseRecognition without the capability");
- return STATUS_ERROR;
- }
- if (mModule == null) {
- mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
- if (mModule == null) {
- Slog.w(TAG, "startKeyphraseRecognition cannot attach to sound trigger module");
- return STATUS_ERROR;
- }
- }
-
- // Unload the previous model if the current one isn't invalid
- // and, it's not the same as the new one.
- // This helps use cache and reuse the model and just start/stop it when necessary.
- if (mCurrentKeyphraseModelHandle != INVALID_VALUE
- && !soundModel.equals(mCurrentSoundModel)) {
- Slog.w(TAG, "Unloading previous sound model");
- int status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
- if (status != SoundTrigger.STATUS_OK) {
- Slog.w(TAG, "unloadSoundModel call failed with " + status);
- }
- internalClearKeyphraseSoundModelLocked();
- mKeyphraseStarted = false;
- }
-
- // If the previous recognition was by a different listener,
- // Notify them that it was stopped.
- if (mKeyphraseListener != null && mKeyphraseListener.asBinder() != listener.asBinder()) {
- Slog.w(TAG, "Canceling previous recognition");
- try {
- mKeyphraseListener.onError(STATUS_ERROR);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onDetectionStopped", e);
- }
- mKeyphraseListener = null;
- }
-
- // Load the sound model if the current one is null.
- int soundModelHandle = mCurrentKeyphraseModelHandle;
- if (mCurrentKeyphraseModelHandle == INVALID_VALUE
- || mCurrentSoundModel == 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");
- }
-
- // Start the recognition.
- mRequested = true;
- mKeyphraseId = keyphraseId;
- mCurrentKeyphraseModelHandle = soundModelHandle;
- mCurrentSoundModel = soundModel;
- 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.
- mKeyphraseListener = listener;
-
- return updateRecognitionLocked(false /* don't notify for synchronous calls */);
- }
- }
-
- /**
- * Stops recognition for the given generic sound model.
+ * Stops recognition for the given generic sound model. This is a wrapper for {@link
+ * #stopRecognition}.
*
* @param modelId The identifier of the generic sound model for which
* the recognition is to be stopped.
- * @param listener The listener for the recognition events related to the given sound model.
+ * @param callback The callback for the recognition events related to the given sound model.
*
* @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
*/
- int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback listener) {
- if (listener == null) {
- return STATUS_ERROR;
- }
-
+ int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
synchronized (mLock) {
+ MetricsLogger.count(mContext, "sth_stop_recognition", 1);
+ if (callback == null || modelId == null) {
+ Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" +
+ modelId);
+ return STATUS_ERROR;
+ }
+
ModelData modelData = mGenericModelDataMap.get(modelId);
if (modelData == null) {
Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
return STATUS_ERROR;
}
- IRecognitionStatusCallback currentCallback = modelData.getCallback();
- if (DBG) {
- Slog.d(TAG, "stopRecognition for modelId=" + modelId
- + ", listener=" + listener.asBinder());
- Slog.d(TAG, "current callback ="
- + (currentCallback == null ? "null" : currentCallback.asBinder()));
- }
-
- if (mModuleProperties == null || mModule == null) {
- Slog.w(TAG, "Attempting stopRecognition without the capability");
- return STATUS_ERROR;
- }
-
- if (currentCallback == null || !modelData.isModelStarted()) {
- // startGenericRecognition hasn't been called or it failed.
- Slog.w(TAG, "Attempting stopGenericRecognition without a successful" +
- " startGenericRecognition");
- return STATUS_ERROR;
- }
- if (currentCallback.asBinder() != listener.asBinder()) {
- // We don't allow a different listener to stop the recognition than the one
- // that started it.
- Slog.w(TAG, "Attempting stopGenericRecognition for another recognition");
- return STATUS_ERROR;
- }
-
- int status = stopGenericRecognitionLocked(modelData,
- false /* don't notify for synchronous calls */);
+ int status = stopRecognition(modelData, callback);
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "stopGenericRecognition failed: " + status);
- return status;
- }
-
- // We leave the sound model loaded but not started, this helps us when we start
- // back.
- // Also clear the internal state once the recognition has been stopped.
- modelData.setLoaded();
- modelData.clearCallback();
- if (!computeRecognitionRunningLocked()) {
- internalClearGlobalStateLocked();
}
return status;
}
@@ -382,47 +338,30 @@
/**
* Stops recognition for the given {@link Keyphrase} if a recognition is
- * currently active.
+ * currently active. This is a wrapper for {@link #stopRecognition()}.
*
* @param keyphraseId The identifier of the keyphrase for which
* the recognition is to be stopped.
- * @param listener The listener for the recognition events related to the given keyphrase.
+ * @param callback The callback for the recognition events related to the given keyphrase.
*
* @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
*/
- int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
- if (listener == null) {
- return STATUS_ERROR;
- }
-
+ int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
synchronized (mLock) {
+ MetricsLogger.count(mContext, "sth_stop_recognition", 1);
+ if (callback == null) {
+ Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" +
+ keyphraseId);
+ return STATUS_ERROR;
+ }
+
if (DBG) {
- Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId
- + ", listener=" + listener.asBinder());
- Slog.d(TAG, "current listener="
- + (mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder()));
+ Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
+ callback.asBinder());
+ Slog.d(TAG, "current callback=" + (mKeyphraseModelData == null ? "null" :
+ mKeyphraseModelData.getCallback().asBinder()));
}
-
- if (mModuleProperties == null || mModule == null) {
- Slog.w(TAG, "Attempting stopRecognition without the capability");
- return STATUS_ERROR;
- }
-
- if (mKeyphraseListener == null) {
- // startRecognition hasn't been called or it failed.
- Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
- return STATUS_ERROR;
- }
- if (mKeyphraseListener.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.
- mRequested = false;
- int status = updateRecognitionLocked(false /* don't notify for synchronous calls */);
+ int status = stopRecognition(mKeyphraseModelData, callback);
if (status != SoundTrigger.STATUS_OK) {
return status;
}
@@ -431,25 +370,115 @@
// back.
// Also clear the internal state once the recognition has been stopped.
internalClearKeyphraseStateLocked();
- internalClearGlobalStateLocked();
return status;
}
}
/**
+ * Stops recognition for the given ModelData instance.
+ *
+ * @param modelData Instance of {@link #ModelData} sound model.
+ * @param callback The callback for the recognition events related to the given keyphrase.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) {
+ synchronized (mLock) {
+ if (callback == null) {
+ return STATUS_ERROR;
+ }
+ if (mModuleProperties == null || mModule == null) {
+ Slog.w(TAG, "Attempting stopRecognition without the capability");
+ return STATUS_ERROR;
+ }
+
+ IRecognitionStatusCallback currentCallback = modelData.getCallback();
+ if (modelData == null || currentCallback == null || !modelData.isModelStarted()) {
+ // startGenericRecognition hasn't been called or it failed.
+ Slog.w(TAG, "Attempting stopGenericRecognition without a successful" +
+ " startGenericRecognition");
+ return STATUS_ERROR;
+ }
+
+ if (currentCallback.asBinder() != callback.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;
+ }
+
+ // Request stop recognition via the update() method.
+ modelData.setRequested(false);
+ int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
+ false /* don't notify for synchronous calls */);
+ if (status != SoundTrigger.STATUS_OK) {
+ return status;
+ }
+
+ // We leave the sound model loaded but not started, this helps us when we start back.
+ // Also clear the internal state once the recognition has been stopped.
+ modelData.setLoaded();
+ modelData.clearCallback();
+ modelData.setRecognitionConfig(null);
+
+ if (!computeRecognitionRunningLocked()) {
+ internalClearGlobalStateLocked();
+ }
+
+ if (modelData.isKeyphraseModel()) {
+ mKeyphraseId = INVALID_VALUE;
+ }
+ return status;
+ }
+ }
+
+ // Stop a previously started model if it was started. Optionally, unload if the previous model
+ // is stale and is about to be replaced.
+ // Needs to be called with the mLock held.
+ private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel,
+ boolean unloadModel) {
+ int status = STATUS_OK;
+ if (modelData.isModelNotLoaded()) {
+ return status;
+ }
+ if (stopModel && modelData.isModelStarted()) {
+ status = stopRecognitionLocked(modelData,
+ false /* don't notify for synchronous calls */);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "stopRecognition failed: " + status);
+ return status;
+ }
+ }
+
+ if (unloadModel && modelData.isModelLoaded()) {
+ Slog.d(TAG, "Unloading previously loaded stale model.");
+ status = mModule.unloadSoundModel(modelData.getHandle());
+ MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "unloadSoundModel call failed with " + status);
+ } else {
+ // Clear the ModelData state if successful.
+ modelData.clearState();
+ modelData.clearCallback();
+ modelData.setRecognitionConfig(null);
+ }
+ }
+ return status;
+ }
+
+ /**
* Stops all recognitions active currently and clears the internal state.
*/
void stopAllRecognitions() {
synchronized (mLock) {
+ MetricsLogger.count(mContext, "sth_stop_all_recognitions", 1);
if (mModuleProperties == null || mModule == null) {
return;
}
// Stop Keyphrase recognition if one exists.
- if (mCurrentKeyphraseModelHandle != INVALID_VALUE) {
-
- mRequested = false;
- int status = updateRecognitionLocked(
+ if (mKeyphraseModelData != null && mKeyphraseModelData.getHandle() != INVALID_VALUE) {
+ mKeyphraseModelData.setRequested(false);
+ int status = updateRecognitionLocked(mKeyphraseModelData, isRecognitionAllowed(),
false /* don't notify for synchronous calls */);
internalClearKeyphraseStateLocked();
}
@@ -457,7 +486,7 @@
// Stop all generic recognition models.
for (ModelData model : mGenericModelDataMap.values()) {
if (model.isModelStarted()) {
- int status = stopGenericRecognitionLocked(model,
+ int status = stopRecognitionLocked(model,
false /* do not notify for synchronous calls */);
if (status != STATUS_OK) {
// What else can we do if there is an error here.
@@ -476,39 +505,40 @@
}
int unloadKeyphraseSoundModel(int keyphraseId) {
- if (mModule == null || mCurrentKeyphraseModelHandle == INVALID_VALUE) {
- return STATUS_ERROR;
- }
- if (mKeyphraseId != keyphraseId) {
- Slog.w(TAG, "Given sound model is not the one loaded.");
- return STATUS_ERROR;
- }
-
synchronized (mLock) {
+ MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
+ if (mModule == null || mKeyphraseModelData == null ||
+ mKeyphraseModelData.getHandle() == INVALID_VALUE) {
+ return STATUS_ERROR;
+ }
+
// Stop recognition if it's the current one.
- mRequested = false;
- int status = updateRecognitionLocked(false /* don't notify */);
+ mKeyphraseModelData.setRequested(false);
+ int status = updateRecognitionLocked(mKeyphraseModelData, isRecognitionAllowed(),
+ false /* don't notify */);
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
}
- status = mModule.unloadSoundModel(mCurrentKeyphraseModelHandle);
+ status = mModule.unloadSoundModel(mKeyphraseModelData.getHandle());
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
}
- internalClearKeyphraseSoundModelLocked();
+ mKeyphraseModelData.clearState();
return status;
}
}
int unloadGenericSoundModel(UUID modelId) {
- if (modelId == null || mModule == null) {
- return STATUS_ERROR;
- }
synchronized (mLock) {
+ MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
+ if (modelId == null || mModule == null) {
+ return STATUS_ERROR;
+ }
ModelData modelData = mGenericModelDataMap.get(modelId);
if (modelData == null) {
- Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" + modelId);
+ Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
+ modelId);
return STATUS_ERROR;
}
if (!modelData.isModelLoaded()) {
@@ -517,7 +547,7 @@
return STATUS_OK;
}
if (modelData.isModelStarted()) {
- int status = stopGenericRecognitionLocked(modelData,
+ int status = stopRecognitionLocked(modelData,
false /* don't notify for synchronous calls */);
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "stopGenericRecognition failed: " + status);
@@ -577,6 +607,7 @@
}
private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+ MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
return;
}
@@ -608,9 +639,11 @@
return;
}
+ model.setRequested(config.allowMultipleTriggers);
// TODO: Remove this block if the lower layer supports multiple triggers.
- if (config.allowMultipleTriggers) {
- startGenericRecognitionLocked(model, true /* notify */);
+ if (model.getRequested()) {
+ updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */,
+ true /* notify */);
}
}
@@ -622,6 +655,7 @@
}
if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
synchronized (mLock) {
+ MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
onSoundModelUpdatedLocked(event);
}
}
@@ -637,6 +671,7 @@
@Override
public void onServiceDied() {
Slog.e(TAG, "onServiceDied!!");
+ MetricsLogger.count(mContext, "sth_service_died", 1);
synchronized (mLock) {
onServiceDiedLocked();
}
@@ -649,7 +684,7 @@
return;
}
mCallActive = callActive;
- updateRecognitionLocked(true /* notify */);
+ updateAllRecognitionsLocked(true /* notify */);
}
private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
@@ -657,7 +692,7 @@
return;
}
mIsPowerSaveMode = isPowerSaveMode;
- updateRecognitionLocked(true /* notify */);
+ updateAllRecognitionsLocked(true /* notify */);
}
private void onSoundModelUpdatedLocked(SoundModelEvent event) {
@@ -669,11 +704,12 @@
return;
}
mServiceDisabled = disabled;
- updateRecognitionLocked(true /* notify */);
+ updateAllRecognitionsLocked(true /* notify */);
}
private void onRecognitionAbortLocked() {
Slog.w(TAG, "Recognition aborted");
+ MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
// If abort has been called, the hardware has already stopped recognition, so we shouldn't
// call it again when we process the state change.
mRecognitionAborted = true;
@@ -681,23 +717,29 @@
private void onRecognitionFailureLocked() {
Slog.w(TAG, "Recognition failure");
+ MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
try {
- if (mKeyphraseListener != null) {
- mKeyphraseListener.onError(STATUS_ERROR);
- }
+ sendErrorCallbacksToAll(STATUS_ERROR);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
} finally {
internalClearKeyphraseStateLocked();
+ internalClearGenericModelStateLocked();
internalClearGlobalStateLocked();
}
}
private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
Slog.i(TAG, "Recognition success");
+ MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
- if (mKeyphraseListener == null) {
- Slog.w(TAG, "received onRecognition event without any listener for it");
+ if (mKeyphraseModelData == null) {
+ Slog.e(TAG, "Received onRecognition event for null keyphrase model data.");
+ return;
+ }
+
+ if (mKeyphraseModelData.getCallback() == null) {
+ Slog.w(TAG, "Received onRecognition event without any listener for it.");
return;
}
@@ -714,30 +756,62 @@
}
try {
- if (mKeyphraseListener != null) {
- mKeyphraseListener.onKeyphraseDetected((KeyphraseRecognitionEvent) event);
- }
+ mKeyphraseModelData.getCallback().onKeyphraseDetected(
+ (KeyphraseRecognitionEvent) event);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
}
- mKeyphraseStarted = false;
- mRequested = mRecognitionConfig.allowMultipleTriggers;
+ mKeyphraseModelData.setStopped();
+
+ RecognitionConfig config = mKeyphraseModelData.getRecognitionConfig();
+ if (config != null) {
+ // Whether we should continue by starting this again.
+ mKeyphraseModelData.setRequested(config.allowMultipleTriggers);
+ }
// TODO: Remove this block if the lower layer supports multiple triggers.
- if (mRequested) {
- updateRecognitionLocked(true /* notify */);
+ if (mKeyphraseModelData.getRequested()) {
+ updateRecognitionLocked(mKeyphraseModelData, isRecognitionAllowed(),
+ true /* notify */);
+ }
+ }
+
+ private void updateAllRecognitionsLocked(boolean notify) {
+ boolean isAllowed = isRecognitionAllowed();
+ // Keyphrase model.
+ if (mKeyphraseModelData != null) {
+ updateRecognitionLocked(mKeyphraseModelData, isAllowed, notify);
+ }
+ for (UUID modelId : mGenericModelDataMap.keySet()) {
+ ModelData modelData = mGenericModelDataMap.get(modelId);
+ updateRecognitionLocked(modelData, isAllowed, notify);
+ }
+ }
+
+ private int updateRecognitionLocked(ModelData model, boolean isAllowed,
+ boolean notify) {
+ boolean start = model.getRequested() && isAllowed;
+ if (start == model.isModelStarted()) {
+ // No-op.
+ return STATUS_OK;
+ }
+ if (start) {
+ return startRecognitionLocked(model, notify);
+ } else {
+ return stopRecognitionLocked(model, notify);
}
}
private void onServiceDiedLocked() {
try {
- if (mKeyphraseListener != null) {
- mKeyphraseListener.onError(SoundTrigger.STATUS_DEAD_OBJECT);
- }
+ MetricsLogger.count(mContext, "sth_service_died", 1);
+ sendErrorCallbacksToAll(SoundTrigger.STATUS_DEAD_OBJECT);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onError", e);
} finally {
- internalClearKeyphraseSoundModelLocked();
+ if (mKeyphraseModelData != null) {
+ mKeyphraseModelData.clearState();
+ }
internalClearKeyphraseStateLocked();
internalClearGenericModelStateLocked();
internalClearGlobalStateLocked();
@@ -748,78 +822,6 @@
}
}
- private int updateRecognitionLocked(boolean notify) {
- if (mModule == null || mModuleProperties == null
- || mCurrentKeyphraseModelHandle == INVALID_VALUE || mKeyphraseListener == null) {
- // Nothing to do here.
- return STATUS_OK;
- }
-
- boolean start = mRequested && !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
- if (start == mKeyphraseStarted) {
- // No-op.
- return STATUS_OK;
- }
-
- // See if the recognition needs to be started.
- if (start) {
- // Start recognition.
- int status = mModule.startRecognition(mCurrentKeyphraseModelHandle,
- mRecognitionConfig);
- if (status != SoundTrigger.STATUS_OK) {
- Slog.w(TAG, "startKeyphraseRecognition failed with " + status);
- // Notify of error if needed.
- if (notify) {
- try {
- mKeyphraseListener.onError(status);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
- }
- }
- } else {
- mKeyphraseStarted = true;
- // Notify of resume if needed.
- if (notify) {
- try {
- mKeyphraseListener.onRecognitionResumed();
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
- }
- }
- }
- return status;
- } else {
- // Stop recognition (only if we haven't been aborted).
- int status = STATUS_OK;
- if (!mRecognitionAborted) {
- status = mModule.stopRecognition(mCurrentKeyphraseModelHandle);
- } else {
- mRecognitionAborted = false;
- }
- if (status != SoundTrigger.STATUS_OK) {
- Slog.w(TAG, "stopRecognition call failed with " + status);
- if (notify) {
- try {
- mKeyphraseListener.onError(status);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
- }
- }
- } else {
- mKeyphraseStarted = false;
- // Notify of pause if needed.
- if (notify) {
- try {
- mKeyphraseListener.onRecognitionPaused();
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
- }
- }
- }
- return status;
- }
- }
-
// internalClearGlobalStateLocked() gets split into two routines. Cleanup that is
// specific to keyphrase sound models named as internalClearKeyphraseStateLocked() and
// internalClearGlobalStateLocked() for global state. The global cleanup routine will be used
@@ -836,12 +838,14 @@
}
private void internalClearKeyphraseStateLocked() {
- mKeyphraseStarted = false;
- mRequested = false;
+ if (mKeyphraseModelData != null) {
+ mKeyphraseModelData.setStopped();
+ mKeyphraseModelData.setRequested(false);
+ mKeyphraseModelData.setRecognitionConfig(null);
+ mKeyphraseModelData.setCallback(null);
+ }
mKeyphraseId = INVALID_VALUE;
- mRecognitionConfig = null;
- mKeyphraseListener = null;
}
private void internalClearGenericModelStateLocked() {
@@ -852,13 +856,6 @@
}
}
- // This routine is a replacement for internalClearSoundModelLocked(). However, we
- // should see why this should be different from internalClearKeyphraseStateLocked().
- private void internalClearKeyphraseSoundModelLocked() {
- mCurrentKeyphraseModelHandle = INVALID_VALUE;
- mCurrentSoundModel = null;
- }
-
class MyCallStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String arg1) {
@@ -888,17 +885,13 @@
pw.print(" module properties=");
pw.println(mModuleProperties == null ? "null" : mModuleProperties);
pw.print(" keyphrase ID="); pw.println(mKeyphraseId);
- pw.print(" sound model handle="); pw.println(mCurrentKeyphraseModelHandle);
- pw.print(" sound model UUID=");
- pw.println(mCurrentSoundModel == null ? "null" : mCurrentSoundModel.uuid);
- pw.print(" current listener=");
- pw.println(mKeyphraseListener == null ? "null" : mKeyphraseListener.asBinder());
- pw.print(" requested="); pw.println(mRequested);
- pw.print(" started="); pw.println(mKeyphraseStarted);
pw.print(" call active="); pw.println(mCallActive);
pw.print(" power save mode active="); pw.println(mIsPowerSaveMode);
pw.print(" service disabled="); pw.println(mServiceDisabled);
+ if (mKeyphraseModelData != null) {
+ pw.println(mKeyphraseModelData.toString());
+ }
}
}
@@ -919,11 +912,25 @@
mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
}
+ // Sends an error callback to all models with a valid registered callback.
+ private void sendErrorCallbacksToAll(int errorCode) throws RemoteException {
+ IRecognitionStatusCallback keyphraseListener = mKeyphraseModelData.getCallback();
+ if (keyphraseListener != null) {
+ keyphraseListener.onError(STATUS_ERROR);
+ }
+ for (UUID modelId: mGenericModelDataMap.keySet()) {
+ ModelData modelData = mGenericModelDataMap.get(modelId);
+ IRecognitionStatusCallback keyphraseCallback = mKeyphraseModelData.getCallback();
+ if (keyphraseCallback != null) {
+ keyphraseCallback.onError(STATUS_ERROR);
+ }
+ }
+ }
+
private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
ModelData modelData = mGenericModelDataMap.get(modelId);
if (modelData == null) {
- modelData = new ModelData(modelId);
- modelData.setTypeGeneric();
+ modelData = ModelData.createGenericModelData(modelId);
mGenericModelDataMap.put(modelId, modelData);
}
return modelData;
@@ -949,25 +956,30 @@
return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
}
- private int startGenericRecognitionLocked(ModelData modelData, boolean notify) {
+ // A single routine that implements the start recognition logic for both generic and keyphrase
+ // models.
+ private int startRecognitionLocked(ModelData modelData, boolean notify) {
IRecognitionStatusCallback callback = modelData.getCallback();
int handle = modelData.getHandle();
RecognitionConfig config = modelData.getRecognitionConfig();
if (callback == null || handle == INVALID_VALUE || config == null) {
// Nothing to do here.
- Slog.w(TAG, "startGenericRecognition: Bad data passed in.");
+ Slog.w(TAG, "startRecognition: Bad data passed in.");
+ MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
return STATUS_ERROR;
}
if (!isRecognitionAllowed()) {
// Nothing to do here.
- Slog.w(TAG, "startGenericRecognition requested but not allowed.");
+ Slog.w(TAG, "startRecognition requested but not allowed.");
+ MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
return STATUS_OK;
}
int status = mModule.startRecognition(handle, config);
if (status != SoundTrigger.STATUS_OK) {
- Slog.w(TAG, "startGenericRecognition failed with " + status);
+ Slog.w(TAG, "startRecognition failed with " + status);
+ MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
// Notify of error if needed.
if (notify) {
try {
@@ -978,6 +990,7 @@
}
} else {
Slog.i(TAG, "startRecognition successful.");
+ MetricsLogger.count(mContext, "sth_start_recognition_success", 1);
modelData.setStarted();
// Notify of resume if needed.
if (notify) {
@@ -988,17 +1001,31 @@
}
}
}
- if (DBG) dumpGenericModelStateLocked();
+ if (DBG) {
+ Slog.d(TAG, "Model being started :" + modelData.toString());
+ }
return status;
}
- private int stopGenericRecognitionLocked(ModelData modelData, boolean notify) {
+ private int stopRecognitionLocked(ModelData modelData, boolean notify) {
IRecognitionStatusCallback callback = modelData.getCallback();
// Stop recognition (only if we haven't been aborted).
- int status = mModule.stopRecognition(modelData.getHandle());
+ int status = STATUS_OK;
+
+ // This logic for "recognition aborted" now works for both generic and keyphrase models.
+ // The idea here is to "skip" the stopRecognition() call if the lower layer has
+ // aborted recognition. Also we "consume" the abort state as well, so if there is another
+ // stopRecognition() request, it will go through -- this seems to have been the previously
+ // intended design.
+ if (!mRecognitionAborted) {
+ status = mModule.stopRecognition(modelData.getHandle());
+ } else {
+ mRecognitionAborted = false;
+ }
if (status != SoundTrigger.STATUS_OK) {
Slog.w(TAG, "stopRecognition call failed with " + status);
+ MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
if (notify) {
try {
callback.onError(status);
@@ -1008,6 +1035,7 @@
}
} else {
modelData.setStopped();
+ MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
// Notify of pause if needed.
if (notify) {
try {
@@ -1017,7 +1045,9 @@
}
}
}
- if (DBG) dumpGenericModelStateLocked();
+ if (DBG) {
+ Slog.d(TAG, "Model being stopped :" + modelData.toString());
+ }
return status;
}
@@ -1035,8 +1065,9 @@
mRecognitionRunning = false;
return mRecognitionRunning;
}
- if (mKeyphraseListener != null && mKeyphraseStarted &&
- mCurrentKeyphraseModelHandle != INVALID_VALUE && mCurrentSoundModel != null) {
+ if (mKeyphraseModelData != null && mKeyphraseModelData.getCallback() != null &&
+ mKeyphraseModelData.isModelStarted() &&
+ mKeyphraseModelData.getHandle() != INVALID_VALUE) {
mRecognitionRunning = true;
return mRecognitionRunning;
}
@@ -1065,26 +1096,55 @@
// One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
private int mModelState;
-
private UUID mModelId;
+ // mRequested captures the explicit intent that a start was requested for this model. We
+ // continue to capture and retain this state even after the model gets started, so that we
+ // know when a model gets stopped due to "other" reasons, that we should start it again.
+ // This was the intended behavior of the "mRequested" variable in the previous version of
+ // this code that we are replicating here.
+ //
+ // The "other" reasons include power save, abort being called from the lower layer (due
+ // to concurrent capture not being supported) and phone call state. Once we recover from
+ // these transient disruptions, we would start such models again where mRequested == true.
+ // Thus, mRequested gets reset only when there is an explicit intent to stop the model
+ // coming from the SoundTriggerService layer that uses this class (and thus eventually
+ // from the app that manages this model).
+ private boolean mRequested = false;
+
// One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
// to SoundModel.TYPE_UNKNOWN;
private int mModelType = SoundModel.TYPE_UNKNOWN;
+
private IRecognitionStatusCallback mCallback = null;
private RecognitionConfig mRecognitionConfig = null;
-
// Model handle is an integer used by the HAL as an identifier for sound
// models.
private int mModelHandle = INVALID_VALUE;
- ModelData(UUID modelId) {
+ // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
+ private SoundModel mSoundModel = null;
+
+ private ModelData(UUID modelId, int modelType) {
mModelId = modelId;
+ // Private constructor, since we require modelType to be one of TYPE_GENERIC,
+ // TYPE_KEYPHRASE or TYPE_UNKNOWN.
+ mModelType = modelType;
}
- synchronized void setTypeGeneric() {
- mModelType = SoundModel.TYPE_GENERIC_SOUND;
+ static ModelData createKeyphraseModelData(UUID modelId) {
+ return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
+ }
+
+ static ModelData createGenericModelData(UUID modelId) {
+ return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
+ }
+
+ // Note that most of the functionality in this Java class will not work for
+ // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
+ static ModelData createModelDataOfUnknownType(UUID modelId) {
+ return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
}
synchronized void setCallback(IRecognitionStatusCallback callback) {
@@ -1099,6 +1159,10 @@
return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
}
+ synchronized boolean isModelNotLoaded() {
+ return mModelState == MODEL_NOTLOADED;
+ }
+
synchronized void setStarted() {
mModelState = MODEL_STARTED;
}
@@ -1136,11 +1200,40 @@
return mModelHandle;
}
+ synchronized UUID getModelId() {
+ return mModelId;
+ }
+
synchronized RecognitionConfig getRecognitionConfig() {
return mRecognitionConfig;
}
- String stateToString() {
+ // Whether a start recognition was requested.
+ synchronized boolean getRequested() {
+ return mRequested;
+ }
+
+ synchronized void setRequested(boolean requested) {
+ mRequested = requested;
+ }
+
+ synchronized void setSoundModel(SoundModel soundModel) {
+ mSoundModel = soundModel;
+ }
+
+ synchronized SoundModel getSoundModel() {
+ return mSoundModel;
+ }
+
+ synchronized int getModelType() {
+ return mModelType;
+ }
+
+ synchronized boolean isKeyphraseModel() {
+ return mModelType == SoundModel.TYPE_KEYPHRASE;
+ }
+
+ synchronized String stateToString() {
switch(mModelState) {
case MODEL_NOTLOADED: return "NOT_LOADED";
case MODEL_LOADED: return "LOADED";
@@ -1149,8 +1242,24 @@
return "Unknown state";
}
- public String toString() {
- return "Handle: " + mModelHandle + "ModelState: " + stateToString();
+ synchronized String requestedToString() {
+ return "Requested: " + (mRequested ? "Yes" : "No");
+ }
+
+ synchronized String callbackToString() {
+ return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
+ }
+
+ synchronized String uuidToString() {
+ return "UUID: " + mModelId;
+ }
+
+ synchronized public String toString() {
+ return "Handle: " + mModelHandle + "\n" +
+ "ModelState: " + stateToString() + "\n" +
+ requestedToString() + "\n" +
+ callbackToString() + "\n" +
+ uuidToString();
}
}
}