From f3e7be00588ea67dfaeb8be3847802d238a9a974 Mon Sep 17 00:00:00 2001 From: Ytai Ben-Tsvi Date: Wed, 6 Apr 2022 15:53:38 -0700 Subject: [PATCH] Wait for an abort event when stopping a model Because the client callbacks from SoundTriggerMiddleware service are async, if a client quickly stops and starts a model, then receives a detection event for that model, it is impossible for it to tell whether the event corresponding to the previous or current session, and thus is unable to reason about the resulting state of the model. To mitigate that, we will always wait for an abort event when a model is stopped before any other operation is attempted. This way the model state stays synchronized. Bug: 191935600 Fixes: 226926627 Test: Manual verification of soundtrigger use-cases. Specifically, quick toggling of Assitant and Now Playing via Settings. Change-Id: Ia1b91e78aa6981a7b3612ae9213af1b37a333d00 --- .../soundtrigger/SoundTriggerHelper.java | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 3aa0ddd26e2d3..b71669046ee9e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -395,20 +395,8 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return STATUS_OK; } - int status = prepareForRecognition(modelData); - if (status != STATUS_OK) { - Slog.w(TAG, "startRecognition failed to prepare model for recognition"); - return status; - } - status = startRecognitionLocked(modelData, + return updateRecognitionLocked(modelData, false /* Don't notify for synchronous calls */); - - // Initialize power save, call active state monitoring logic. - if (status == STATUS_OK) { - initializeDeviceStateListeners(); - } - - return status; } } @@ -557,7 +545,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } - if (unloadModel && modelData.isModelLoaded()) { + if (unloadModel && (modelData.isModelLoaded() || modelData.isStopPending())) { Slog.d(TAG, "Unloading previously loaded stale model."); if (mModule == null) { return STATUS_ERROR; @@ -917,7 +905,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { Slog.w(TAG, "Recognition aborted"); MetricsLogger.count(mContext, "sth_recognition_aborted", 1); ModelData modelData = getModelDataForLocked(event.soundModelHandle); - if (modelData != null && modelData.isModelStarted()) { + if (modelData != null && (modelData.isModelStarted() || modelData.isStopPending())) { modelData.setStopped(); try { IRecognitionStatusCallback callback = modelData.getCallback(); @@ -929,6 +917,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } catch (RemoteException e) { Slog.w(TAG, "RemoteException in onRecognitionPaused", e); } + updateRecognitionLocked(modelData, true); } } @@ -1008,16 +997,22 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) { boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model); - if (shouldStartModel == model.isModelStarted()) { + if (shouldStartModel == model.isModelStarted() || model.isStopPending()) { // No-op. return STATUS_OK; } if (shouldStartModel) { int status = prepareForRecognition(model); if (status != STATUS_OK) { + Slog.w(TAG, "startRecognition failed to prepare model for recognition"); return status; } - return startRecognitionLocked(model, notifyClientOnError); + status = startRecognitionLocked(model, notifyClientOnError); + // Initialize power save, call active state monitoring logic. + if (status == STATUS_OK) { + initializeDeviceStateListeners(); + } + return status; } else { return stopRecognitionLocked(model, notifyClientOnError); } @@ -1200,7 +1195,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { if (mModule == null) { return; } - if (modelData.isModelStarted()) { + if (modelData.isStopPending()) { + // No need to wait for the stop to be confirmed. + modelData.setStopped(); + } else if (modelData.isModelStarted()) { Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle()); if (mModule.stopRecognition(modelData.getHandle()) == STATUS_OK) { modelData.setStopped(); @@ -1417,7 +1415,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } } else { - modelData.setStopped(); + modelData.setStopPending(); MetricsLogger.count(mContext, "sth_stop_recognition_success", 1); // Notify of pause if needed. if (notify) { @@ -1472,6 +1470,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // Started implies model was successfully loaded and start was called. static final int MODEL_STARTED = 2; + // Model stop request has been sent. Waiting for an event to signal model being stopped. + static final int MODEL_STOP_PENDING = 3; + // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded). private int mModelState; private UUID mModelId; @@ -1549,6 +1550,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return mModelState == MODEL_NOTLOADED; } + synchronized boolean isStopPending() { + return mModelState == MODEL_STOP_PENDING; + } + synchronized void setStarted() { mModelState = MODEL_STARTED; } @@ -1557,6 +1562,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { mModelState = MODEL_LOADED; } + synchronized void setStopPending() { + mModelState = MODEL_STOP_PENDING; + } + synchronized void setLoaded() { mModelState = MODEL_LOADED; }