Sound Trigger Middleware is a system service that exposes sound trigger functionality (low-power detection of acoustic events) to applications and higher-level system service.
It has the following roles:
The service implementation can be divided into three main layers:
This layer implements the ISoundTriggerHal
interface, which is the version-agnostic representation of the sound trigger HAL driver. It has two main implementations, SoundTriggerHw2Compat
and SoundTriggerHw3Compat
responsible for adapting to V2.x and V3 HAL drivers, respectively, including supporting their respective minor-version differences.
This layer also includes several ISoundTriggerHal
decorators, such as SoundTriggerHalWatchdog
that enforces deadlines on calls into the HAL, and SoundTriggerHalEnforcer
which enforces that the HAL respects the expected protocol.
The decorator-based design is an effective tool for separation of aspects and modularity, thus keeping classes relatively small and focused on one concern. It is also very effective for testability by following dependency injection principles.
This layer also uses a decorator-based design for separation of concerns. The main interface being decorated is ISoundTriggerMiddlwareInternal
, which closely follows the external-facing AIDL interface, ISoundTriggerMiddlewareService
.
Each of the decorators serves a focused purpose: for example, SoundTriggerMiddlwarePermission
deals with enforcing permissions required for the various methods, SoundTriggerMiddlewareLogging
logs all API usage, SoundTriggerMiddlewareValidation
enforces correct usage of the protocol and isolates client errors from internal server errors.
At the bottom of this decorator stack is SoundTriggerMiddlewareImpl
/ SoundTriggerModule
, which are the adapter between ISoundTriggerHal
and ISoundTriggerMiddlwareInternal
, introducing the notion of having separate client sessions sharing the same HAL.
This layer ties everything together. It instantiates the actual system service and the decorator stack. It also provides concrete connections to the Audio service (for negotiating sessions shared between Audio and Sound Trigger and for notifications about audio recording) and to the various HAL factories.
This is the only layer that makes strong assumptions about the environment instead of relying on abstractions.
We follow conventions for usage of exceptions in the service, in order to correctly and consistently distinguish the following cases:
The SoundTriggerMiddlewarePermission
class would reject any calls from unauthorized clients, responding with the appropriate exception.
The SoundTriggerMiddlewareValidation
class does much of this separation. By validating the client‘s data and state, it would throw a relevant RuntimeException
exception to the client without passing the requests down to the lower layers. Once that is done, any exception thrown from the underlying implementation can be assumed to be not the client’s fault. If caught, they will be classified according to the following rule:
RecoverableException
s, they represent category #4 above, and will be presented to the client as ServiceSpecificException
s with the same error code.ServiceSpecificException(Status.INTERNAL_ERROR)
.Internally, we would throw RecoverableException
whenever appropriate. Whenever a HAL malfunctions, SoundTriggerHalEnforcer
is responsible for rebooting it and throwing an exception. A HAL death is considered a valid failure mode, and thus result in RecoverableException(Status.DEAD_OBJECT)
, which ends up as a ServiceSpecificException(Status.DEAD_OBJECT)
on the client side.
This component has some tricky thread synchronization considerations due to its layered design and due to the fact that it is involved in both in-bound and out-bound calls from / to external components.
The following mutexes need to be considered:
ExternalCaptureStateTracker.setCaptureState()
call stack AND to be acquired from within our calls into AudioSessionProvider.acquireSession()
/ AudioSessionProvider.releaseSession()
.To avoid potential deadlocks, a strict locking order must be ensured whenever nesting locks. The order is:
ISoundTriggerHw2
.ISoundTriggerHw2
all the way down to the HAL.In order to enforce this order, some conventions are established around when it is safe for a module to call another module, while having its local mutex(es) held:
stopRecognition()
and unloadModel()
work differently. They guarantee that once they return, the callbacks associated with them will no longer be called. This implies that they have to block until any pending callbacks are done processing and since these callbacks are potentially holding locks of higher-order mutexes, we must not be holding a local mutex while calling down. The proper sequence for these calls is:SoundTriggerMiddlewareImpl
/ SoundTriggerModule
into the audio policy service via acquireSession()
/ releaseSession()
while holding the local lock is legal.setCaptureState()
calls, originating from Audio Policy Service, into the lower layers of the stack may call into the HAL (specifically, they must invoke stopRecognition()
, but must not block on callbacks. For this reason, SoundTriggerHw2ConcurrentCaptureHandler
, which is the recipient of these calls, features a buffer and an additional thread, which allows the actual stopping to be synchronous, as required, without having to block the call upon higher layers processing the callbacks.