Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b660525 authored by Ytai Ben-Tsvi's avatar Ytai Ben-Tsvi
Browse files

Generate an abort event when stopping

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 send an abort event if a model is
stopped while it was in an active state. This way the client can
consider the model active (even after stop()) until an event is
received for that model.

Bug: 191935600
Test: Manual verification of soundtrigger use-cases.
Test: atest SoundTriggerMiddlewareImplTest
Change-Id: I59555b024df54ca90ae7573acb38e114dac0c130
parent 25ec8c6d
Loading
Loading
Loading
Loading
+42 −6
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.media.soundtrigger.Properties;
import android.media.soundtrigger.RecognitionConfig;
import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger.SoundModelType;
import android.media.soundtrigger.Status;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -305,9 +306,12 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo

        @Override
        public void stopRecognition(int modelHandle) {
            Model model;
            synchronized (SoundTriggerModule.this) {
                mLoadedModels.get(modelHandle).stopRecognition();
                checkValid();
                model = mLoadedModels.get(modelHandle);
            }
            model.stopRecognition();
        }

        @Override
@@ -374,6 +378,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
        private class Model implements ISoundTriggerHal.ModelCallback {
            public int mHandle;
            private ModelState mState = ModelState.INIT;
            private int mType = SoundModelType.INVALID;
            private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;

            private @NonNull
@@ -390,6 +395,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
                    SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                mSession = audioSession;
                mHandle = mHalService.loadSoundModel(model, this);
                mType = SoundModelType.GENERIC;
                setState(ModelState.LOADED);
                mLoadedModels.put(mHandle, this);
                return mHandle;
@@ -399,7 +405,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
                    SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession) {
                mSession = audioSession;
                mHandle = mHalService.loadPhraseSoundModel(model, this);

                mType = SoundModelType.KEYPHRASE;
                setState(ModelState.LOADED);
                mLoadedModels.put(mHandle, this);
                return mHandle;
@@ -422,13 +428,42 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
            }

            private void stopRecognition() {
                synchronized (SoundTriggerModule.this) {
                    if (getState() == ModelState.LOADED) {
                        // This call is idempotent in order to avoid races.
                        return;
                    }
                }
                // This must be invoked outside the lock.
                mHalService.stopRecognition(mHandle);

                // No more callbacks for this model after this point.
                synchronized (SoundTriggerModule.this) {
                    // Generate an abortion callback to the client if the model is still active.
                    if (getState() == ModelState.ACTIVE) {
                        if (mCallback != null) {
                            try {
                                switch (mType) {
                                    case SoundModelType.GENERIC:
                                        mCallback.onRecognition(mHandle, AidlUtil.newAbortEvent(),
                                                mSession.mSessionHandle);
                                        break;
                                    case SoundModelType.KEYPHRASE:
                                        mCallback.onPhraseRecognition(mHandle,
                                                AidlUtil.newAbortPhraseEvent(),
                                                mSession.mSessionHandle);
                                        break;
                                    default:
                                        throw new RuntimeException(
                                                "Unexpected model type: " + mType);
                                }
                            } catch (RemoteException e) {
                            }
                        }
                        setState(ModelState.LOADED);
                    }
                }
            }

            /** Request a forced recognition event. Will do nothing if recognition is inactive. */
            private void forceRecognitionEvent() {
@@ -518,4 +553,5 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
            }
        }
    }

}
+10 −0
Original line number Diff line number Diff line
@@ -224,6 +224,11 @@ public class SoundTriggerMiddlewareImplTest {
        // Stop the recognition.
        stopRecognition(module, handle, hwHandle);

        ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                RecognitionEvent.class);
        verify(callback).onRecognition(eq(handle), eventCaptor.capture(), eq(101));
        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);

        // Unload the model.
        unloadModel(module, handle, hwHandle);
        module.detach();
@@ -268,6 +273,11 @@ public class SoundTriggerMiddlewareImplTest {
        // Stop the recognition.
        stopRecognition(module, handle, hwHandle);

        ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                PhraseRecognitionEvent.class);
        verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture(), eq(101));
        assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);

        // Unload the model.
        unloadModel(module, handle, hwHandle);
        module.detach();