You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1119 lines
36 KiB
1119 lines
36 KiB
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#define LOG_TAG "SoundTriggerHwService"
|
|
//#define LOG_NDEBUG 0
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <pthread.h>
|
|
|
|
#include <audio_utils/clock.h>
|
|
#include <system/sound_trigger.h>
|
|
#include <cutils/atomic.h>
|
|
#include <cutils/properties.h>
|
|
#include <hardware/hardware.h>
|
|
#include <media/AudioSystem.h>
|
|
#include <mediautils/ServiceUtilities.h>
|
|
#include <utils/Errors.h>
|
|
#include <utils/Log.h>
|
|
#include <binder/IServiceManager.h>
|
|
#include <binder/MemoryBase.h>
|
|
#include <binder/MemoryHeapBase.h>
|
|
#include <system/sound_trigger.h>
|
|
#include "SoundTriggerHwService.h"
|
|
|
|
#define HW_MODULE_PREFIX "primary"
|
|
namespace android {
|
|
|
|
SoundTriggerHwService::SoundTriggerHwService()
|
|
: BnSoundTriggerHwService(),
|
|
mNextUniqueId(1),
|
|
mMemoryDealer(new MemoryDealer(1024 * 1024, "SoundTriggerHwService")),
|
|
mCaptureState(false)
|
|
{
|
|
}
|
|
|
|
void SoundTriggerHwService::onFirstRef()
|
|
{
|
|
int rc;
|
|
|
|
sp<SoundTriggerHalInterface> halInterface =
|
|
SoundTriggerHalInterface::connectModule(HW_MODULE_PREFIX);
|
|
|
|
if (halInterface == 0) {
|
|
ALOGW("could not connect to HAL");
|
|
return;
|
|
}
|
|
sound_trigger_module_descriptor descriptor;
|
|
rc = halInterface->getProperties(&descriptor.properties);
|
|
if (rc != 0) {
|
|
ALOGE("could not read implementation properties");
|
|
return;
|
|
}
|
|
descriptor.handle =
|
|
(sound_trigger_module_handle_t)android_atomic_inc(&mNextUniqueId);
|
|
ALOGI("loaded default module %s, handle %d", descriptor.properties.description,
|
|
descriptor.handle);
|
|
|
|
sp<Module> module = new Module(this, halInterface, descriptor);
|
|
mModules.add(descriptor.handle, module);
|
|
mCallbackThread = new CallbackThread(this);
|
|
}
|
|
|
|
SoundTriggerHwService::~SoundTriggerHwService()
|
|
{
|
|
if (mCallbackThread != 0) {
|
|
mCallbackThread->exit();
|
|
}
|
|
}
|
|
|
|
status_t SoundTriggerHwService::listModules(const String16& opPackageName,
|
|
struct sound_trigger_module_descriptor *modules,
|
|
uint32_t *numModules)
|
|
{
|
|
ALOGV("listModules");
|
|
if (!captureHotwordAllowed(opPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return PERMISSION_DENIED;
|
|
}
|
|
|
|
AutoMutex lock(mServiceLock);
|
|
if (numModules == NULL || (*numModules != 0 && modules == NULL)) {
|
|
return BAD_VALUE;
|
|
}
|
|
size_t maxModules = *numModules;
|
|
*numModules = mModules.size();
|
|
for (size_t i = 0; i < mModules.size() && i < maxModules; i++) {
|
|
modules[i] = mModules.valueAt(i)->descriptor();
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::attach(const String16& opPackageName,
|
|
const sound_trigger_module_handle_t handle,
|
|
const sp<ISoundTriggerClient>& client,
|
|
sp<ISoundTrigger>& moduleInterface)
|
|
{
|
|
ALOGV("attach module %d", handle);
|
|
if (!captureHotwordAllowed(opPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return PERMISSION_DENIED;
|
|
}
|
|
|
|
AutoMutex lock(mServiceLock);
|
|
moduleInterface.clear();
|
|
if (client == 0) {
|
|
return BAD_VALUE;
|
|
}
|
|
ssize_t index = mModules.indexOfKey(handle);
|
|
if (index < 0) {
|
|
return BAD_VALUE;
|
|
}
|
|
sp<Module> module = mModules.valueAt(index);
|
|
|
|
sp<ModuleClient> moduleClient = module->addClient(client, opPackageName);
|
|
if (moduleClient == 0) {
|
|
return NO_INIT;
|
|
}
|
|
|
|
moduleClient->setCaptureState_l(mCaptureState);
|
|
moduleInterface = moduleClient;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::setCaptureState(bool active)
|
|
{
|
|
ALOGV("setCaptureState %d", active);
|
|
AutoMutex lock(mServiceLock);
|
|
mCaptureState = active;
|
|
for (size_t i = 0; i < mModules.size(); i++) {
|
|
mModules.valueAt(i)->setCaptureState_l(active);
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
static const int kDumpLockTimeoutNs = 1 * NANOS_PER_SECOND;
|
|
|
|
static bool dumpTryLock(Mutex& mutex)
|
|
{
|
|
status_t err = mutex.timedLock(kDumpLockTimeoutNs);
|
|
return err == NO_ERROR;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::dump(int fd, const Vector<String16>& args __unused) {
|
|
String8 result;
|
|
if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
|
|
result.appendFormat("Permission Denial: can't dump SoundTriggerHwService");
|
|
write(fd, result.string(), result.size());
|
|
} else {
|
|
bool locked = dumpTryLock(mServiceLock);
|
|
// failed to lock - SoundTriggerHwService is probably deadlocked
|
|
if (!locked) {
|
|
result.append("SoundTriggerHwService may be deadlocked\n");
|
|
write(fd, result.string(), result.size());
|
|
}
|
|
|
|
if (locked) mServiceLock.unlock();
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::onTransact(
|
|
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
|
|
return BnSoundTriggerHwService::onTransact(code, data, reply, flags);
|
|
}
|
|
|
|
|
|
// static
|
|
void SoundTriggerHwService::recognitionCallback(struct sound_trigger_recognition_event *event,
|
|
void *cookie)
|
|
{
|
|
Module *module = (Module *)cookie;
|
|
if (module == NULL) {
|
|
return;
|
|
}
|
|
sp<SoundTriggerHwService> service = module->service().promote();
|
|
if (service == 0) {
|
|
return;
|
|
}
|
|
|
|
service->sendRecognitionEvent(event, module);
|
|
}
|
|
|
|
sp<IMemory> SoundTriggerHwService::prepareRecognitionEvent(
|
|
struct sound_trigger_recognition_event *event)
|
|
{
|
|
AutoMutex lock(mMemoryDealerLock);
|
|
sp<IMemory> eventMemory;
|
|
|
|
//sanitize event
|
|
switch (event->type) {
|
|
case SOUND_MODEL_TYPE_KEYPHRASE:
|
|
ALOGW_IF(event->data_size != 0 && event->data_offset !=
|
|
sizeof(struct sound_trigger_phrase_recognition_event),
|
|
"prepareRecognitionEvent(): invalid data offset %u for keyphrase event type",
|
|
event->data_offset);
|
|
event->data_offset = sizeof(struct sound_trigger_phrase_recognition_event);
|
|
break;
|
|
case SOUND_MODEL_TYPE_GENERIC:
|
|
ALOGW_IF(event->data_size != 0 && event->data_offset !=
|
|
sizeof(struct sound_trigger_generic_recognition_event),
|
|
"prepareRecognitionEvent(): invalid data offset %u for generic event type",
|
|
event->data_offset);
|
|
event->data_offset = sizeof(struct sound_trigger_generic_recognition_event);
|
|
break;
|
|
case SOUND_MODEL_TYPE_UNKNOWN:
|
|
ALOGW_IF(event->data_size != 0 && event->data_offset !=
|
|
sizeof(struct sound_trigger_recognition_event),
|
|
"prepareRecognitionEvent(): invalid data offset %u for unknown event type",
|
|
event->data_offset);
|
|
event->data_offset = sizeof(struct sound_trigger_recognition_event);
|
|
break;
|
|
default:
|
|
return eventMemory;
|
|
}
|
|
|
|
size_t size = event->data_offset + event->data_size;
|
|
eventMemory = mMemoryDealer->allocate(size);
|
|
if (eventMemory == 0 || eventMemory->pointer() == NULL) {
|
|
eventMemory.clear();
|
|
return eventMemory;
|
|
}
|
|
memcpy(eventMemory->pointer(), event, size);
|
|
|
|
return eventMemory;
|
|
}
|
|
|
|
void SoundTriggerHwService::sendRecognitionEvent(struct sound_trigger_recognition_event *event,
|
|
Module *module)
|
|
{
|
|
if (module == NULL) {
|
|
return;
|
|
}
|
|
sp<IMemory> eventMemory = prepareRecognitionEvent(event);
|
|
if (eventMemory == 0) {
|
|
return;
|
|
}
|
|
|
|
sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_RECOGNITION,
|
|
eventMemory);
|
|
callbackEvent->setModule(module);
|
|
sendCallbackEvent(callbackEvent);
|
|
}
|
|
|
|
// static
|
|
void SoundTriggerHwService::soundModelCallback(struct sound_trigger_model_event *event,
|
|
void *cookie)
|
|
{
|
|
Module *module = (Module *)cookie;
|
|
if (module == NULL) {
|
|
return;
|
|
}
|
|
sp<SoundTriggerHwService> service = module->service().promote();
|
|
if (service == 0) {
|
|
return;
|
|
}
|
|
|
|
service->sendSoundModelEvent(event, module);
|
|
}
|
|
|
|
sp<IMemory> SoundTriggerHwService::prepareSoundModelEvent(struct sound_trigger_model_event *event)
|
|
{
|
|
AutoMutex lock(mMemoryDealerLock);
|
|
sp<IMemory> eventMemory;
|
|
|
|
size_t size = event->data_offset + event->data_size;
|
|
eventMemory = mMemoryDealer->allocate(size);
|
|
if (eventMemory == 0 || eventMemory->pointer() == NULL) {
|
|
eventMemory.clear();
|
|
return eventMemory;
|
|
}
|
|
memcpy(eventMemory->pointer(), event, size);
|
|
|
|
return eventMemory;
|
|
}
|
|
|
|
void SoundTriggerHwService::sendSoundModelEvent(struct sound_trigger_model_event *event,
|
|
Module *module)
|
|
{
|
|
sp<IMemory> eventMemory = prepareSoundModelEvent(event);
|
|
if (eventMemory == 0) {
|
|
return;
|
|
}
|
|
sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_SOUNDMODEL,
|
|
eventMemory);
|
|
callbackEvent->setModule(module);
|
|
sendCallbackEvent(callbackEvent);
|
|
}
|
|
|
|
|
|
sp<IMemory> SoundTriggerHwService::prepareServiceStateEvent(sound_trigger_service_state_t state)
|
|
{
|
|
AutoMutex lock(mMemoryDealerLock);
|
|
sp<IMemory> eventMemory;
|
|
|
|
size_t size = sizeof(sound_trigger_service_state_t);
|
|
eventMemory = mMemoryDealer->allocate(size);
|
|
if (eventMemory == 0 || eventMemory->pointer() == NULL) {
|
|
eventMemory.clear();
|
|
return eventMemory;
|
|
}
|
|
*((sound_trigger_service_state_t *)eventMemory->pointer()) = state;
|
|
return eventMemory;
|
|
}
|
|
|
|
void SoundTriggerHwService::sendServiceStateEvent(sound_trigger_service_state_t state,
|
|
Module *module)
|
|
{
|
|
sp<IMemory> eventMemory = prepareServiceStateEvent(state);
|
|
if (eventMemory == 0) {
|
|
return;
|
|
}
|
|
sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_SERVICE_STATE,
|
|
eventMemory);
|
|
callbackEvent->setModule(module);
|
|
sendCallbackEvent(callbackEvent);
|
|
}
|
|
|
|
void SoundTriggerHwService::sendServiceStateEvent(sound_trigger_service_state_t state,
|
|
ModuleClient *moduleClient)
|
|
{
|
|
sp<IMemory> eventMemory = prepareServiceStateEvent(state);
|
|
if (eventMemory == 0) {
|
|
return;
|
|
}
|
|
sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_SERVICE_STATE,
|
|
eventMemory);
|
|
callbackEvent->setModuleClient(moduleClient);
|
|
sendCallbackEvent(callbackEvent);
|
|
}
|
|
|
|
void SoundTriggerHwService::sendCallbackEvent(const sp<CallbackEvent>& event)
|
|
{
|
|
mCallbackThread->sendCallbackEvent(event);
|
|
}
|
|
|
|
void SoundTriggerHwService::onCallbackEvent(const sp<CallbackEvent>& event)
|
|
{
|
|
ALOGV("onCallbackEvent");
|
|
sp<Module> module;
|
|
sp<ModuleClient> moduleClient;
|
|
{
|
|
AutoMutex lock(mServiceLock);
|
|
//CallbackEvent is either for Module or ModuleClient
|
|
module = event->mModule.promote();
|
|
if (module == 0) {
|
|
moduleClient = event->mModuleClient.promote();
|
|
if (moduleClient == 0) {
|
|
return;
|
|
}
|
|
} else {
|
|
// Sanity check on this being a Module we know about.
|
|
bool foundModule = false;
|
|
for (size_t i = 0; i < mModules.size(); i++) {
|
|
if (mModules.valueAt(i).get() == module.get()) {
|
|
foundModule = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundModule) {
|
|
ALOGE("onCallbackEvent for unknown module");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (module != 0) {
|
|
ALOGV("onCallbackEvent for module");
|
|
module->onCallbackEvent(event);
|
|
} else if (moduleClient != 0) {
|
|
ALOGV("onCallbackEvent for moduleClient");
|
|
moduleClient->onCallbackEvent(event);
|
|
}
|
|
{
|
|
AutoMutex lock(mServiceLock);
|
|
// clear now to execute with mServiceLock locked
|
|
event->mMemory.clear();
|
|
}
|
|
}
|
|
|
|
#undef LOG_TAG
|
|
#define LOG_TAG "SoundTriggerHwService::CallbackThread"
|
|
|
|
SoundTriggerHwService::CallbackThread::CallbackThread(const wp<SoundTriggerHwService>& service)
|
|
: mService(service)
|
|
{
|
|
}
|
|
|
|
SoundTriggerHwService::CallbackThread::~CallbackThread()
|
|
{
|
|
while (!mEventQueue.isEmpty()) {
|
|
mEventQueue[0]->mMemory.clear();
|
|
mEventQueue.removeAt(0);
|
|
}
|
|
}
|
|
|
|
void SoundTriggerHwService::CallbackThread::onFirstRef()
|
|
{
|
|
run("soundTrigger cbk", ANDROID_PRIORITY_URGENT_AUDIO);
|
|
}
|
|
|
|
bool SoundTriggerHwService::CallbackThread::threadLoop()
|
|
{
|
|
while (!exitPending()) {
|
|
sp<CallbackEvent> event;
|
|
sp<SoundTriggerHwService> service;
|
|
{
|
|
Mutex::Autolock _l(mCallbackLock);
|
|
while (mEventQueue.isEmpty() && !exitPending()) {
|
|
ALOGV("CallbackThread::threadLoop() sleep");
|
|
mCallbackCond.wait(mCallbackLock);
|
|
ALOGV("CallbackThread::threadLoop() wake up");
|
|
}
|
|
if (exitPending()) {
|
|
break;
|
|
}
|
|
event = mEventQueue[0];
|
|
mEventQueue.removeAt(0);
|
|
service = mService.promote();
|
|
}
|
|
if (service != 0) {
|
|
service->onCallbackEvent(event);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SoundTriggerHwService::CallbackThread::exit()
|
|
{
|
|
Mutex::Autolock _l(mCallbackLock);
|
|
requestExit();
|
|
mCallbackCond.broadcast();
|
|
}
|
|
|
|
void SoundTriggerHwService::CallbackThread::sendCallbackEvent(
|
|
const sp<SoundTriggerHwService::CallbackEvent>& event)
|
|
{
|
|
AutoMutex lock(mCallbackLock);
|
|
mEventQueue.add(event);
|
|
mCallbackCond.signal();
|
|
}
|
|
|
|
SoundTriggerHwService::CallbackEvent::CallbackEvent(event_type type, sp<IMemory> memory)
|
|
: mType(type), mMemory(memory)
|
|
{
|
|
}
|
|
|
|
SoundTriggerHwService::CallbackEvent::~CallbackEvent()
|
|
{
|
|
}
|
|
|
|
|
|
#undef LOG_TAG
|
|
#define LOG_TAG "SoundTriggerHwService::Module"
|
|
|
|
SoundTriggerHwService::Module::Module(const sp<SoundTriggerHwService>& service,
|
|
const sp<SoundTriggerHalInterface>& halInterface,
|
|
sound_trigger_module_descriptor descriptor)
|
|
: mService(service), mHalInterface(halInterface), mDescriptor(descriptor),
|
|
mServiceState(SOUND_TRIGGER_STATE_NO_INIT)
|
|
{
|
|
}
|
|
|
|
SoundTriggerHwService::Module::~Module() {
|
|
mModuleClients.clear();
|
|
}
|
|
|
|
sp<SoundTriggerHwService::ModuleClient>
|
|
SoundTriggerHwService::Module::addClient(const sp<ISoundTriggerClient>& client,
|
|
const String16& opPackageName)
|
|
{
|
|
AutoMutex lock(mLock);
|
|
sp<ModuleClient> moduleClient;
|
|
|
|
for (size_t i = 0; i < mModuleClients.size(); i++) {
|
|
if (mModuleClients[i]->client() == client) {
|
|
// Client already present, reuse client
|
|
return moduleClient;
|
|
}
|
|
}
|
|
moduleClient = new ModuleClient(this, client, opPackageName);
|
|
|
|
ALOGV("addClient() client %p", moduleClient.get());
|
|
mModuleClients.add(moduleClient);
|
|
|
|
return moduleClient;
|
|
}
|
|
|
|
void SoundTriggerHwService::Module::detach(const sp<ModuleClient>& moduleClient)
|
|
{
|
|
ALOGV("Module::detach()");
|
|
Vector<audio_session_t> releasedSessions;
|
|
|
|
{
|
|
AutoMutex lock(mLock);
|
|
ssize_t index = -1;
|
|
|
|
for (size_t i = 0; i < mModuleClients.size(); i++) {
|
|
if (mModuleClients[i] == moduleClient) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
if (index == -1) {
|
|
return;
|
|
}
|
|
|
|
ALOGV("remove client %p", moduleClient.get());
|
|
mModuleClients.removeAt(index);
|
|
|
|
// Iterate in reverse order as models are removed from list inside the loop.
|
|
for (size_t i = mModels.size(); i > 0; i--) {
|
|
sp<Model> model = mModels.valueAt(i - 1);
|
|
if (moduleClient == model->mModuleClient) {
|
|
mModels.removeItemsAt(i - 1);
|
|
ALOGV("detach() unloading model %d", model->mHandle);
|
|
if (mHalInterface != 0) {
|
|
if (model->mState == Model::STATE_ACTIVE) {
|
|
mHalInterface->stopRecognition(model->mHandle);
|
|
}
|
|
mHalInterface->unloadSoundModel(model->mHandle);
|
|
}
|
|
releasedSessions.add(model->mCaptureSession);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < releasedSessions.size(); i++) {
|
|
// do not call AudioSystem methods with mLock held
|
|
AudioSystem::releaseSoundTriggerSession(releasedSessions[i]);
|
|
}
|
|
}
|
|
|
|
status_t SoundTriggerHwService::Module::loadSoundModel(const sp<IMemory>& modelMemory,
|
|
sp<ModuleClient> moduleClient,
|
|
sound_model_handle_t *handle)
|
|
{
|
|
ALOGV("loadSoundModel() handle");
|
|
if (mHalInterface == 0) {
|
|
return NO_INIT;
|
|
}
|
|
|
|
struct sound_trigger_sound_model *sound_model =
|
|
(struct sound_trigger_sound_model *)modelMemory->pointer();
|
|
|
|
size_t structSize;
|
|
if (sound_model->type == SOUND_MODEL_TYPE_KEYPHRASE) {
|
|
structSize = sizeof(struct sound_trigger_phrase_sound_model);
|
|
} else {
|
|
structSize = sizeof(struct sound_trigger_sound_model);
|
|
}
|
|
|
|
if (sound_model->data_offset < structSize ||
|
|
sound_model->data_size > (UINT_MAX - sound_model->data_offset) ||
|
|
modelMemory->size() < sound_model->data_offset ||
|
|
sound_model->data_size > (modelMemory->size() - sound_model->data_offset)) {
|
|
android_errorWriteLog(0x534e4554, "30148546");
|
|
ALOGE("loadSoundModel() data_size is too big");
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
audio_session_t session;
|
|
audio_io_handle_t ioHandle;
|
|
audio_devices_t device;
|
|
// do not call AudioSystem methods with mLock held
|
|
status_t status = AudioSystem::acquireSoundTriggerSession(&session, &ioHandle, &device);
|
|
if (status != NO_ERROR) {
|
|
return status;
|
|
}
|
|
|
|
{
|
|
AutoMutex lock(mLock);
|
|
|
|
if (mModels.size() >= mDescriptor.properties.max_sound_models) {
|
|
ALOGW("loadSoundModel(): Not loading, max number of models (%d) would be exceeded",
|
|
mDescriptor.properties.max_sound_models);
|
|
status = INVALID_OPERATION;
|
|
goto exit;
|
|
}
|
|
|
|
status = mHalInterface->loadSoundModel(sound_model,
|
|
SoundTriggerHwService::soundModelCallback,
|
|
this, handle);
|
|
if (status != NO_ERROR) {
|
|
goto exit;
|
|
}
|
|
|
|
sp<Model> model = new Model(*handle, session, ioHandle, device, sound_model->type,
|
|
moduleClient);
|
|
mModels.replaceValueFor(*handle, model);
|
|
}
|
|
exit:
|
|
if (status != NO_ERROR) {
|
|
// do not call AudioSystem methods with mLock held
|
|
AudioSystem::releaseSoundTriggerSession(session);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::Module::unloadSoundModel(sound_model_handle_t handle)
|
|
{
|
|
ALOGV("unloadSoundModel() model handle %d", handle);
|
|
status_t status;
|
|
audio_session_t session;
|
|
|
|
{
|
|
AutoMutex lock(mLock);
|
|
if (mHalInterface == 0) {
|
|
return NO_INIT;
|
|
}
|
|
ssize_t index = mModels.indexOfKey(handle);
|
|
if (index < 0) {
|
|
return BAD_VALUE;
|
|
}
|
|
sp<Model> model = mModels.valueAt(index);
|
|
mModels.removeItem(handle);
|
|
if (model->mState == Model::STATE_ACTIVE) {
|
|
mHalInterface->stopRecognition(model->mHandle);
|
|
model->mState = Model::STATE_IDLE;
|
|
}
|
|
status = mHalInterface->unloadSoundModel(handle);
|
|
session = model->mCaptureSession;
|
|
}
|
|
// do not call AudioSystem methods with mLock held
|
|
AudioSystem::releaseSoundTriggerSession(session);
|
|
return status;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::Module::startRecognition(sound_model_handle_t handle,
|
|
const sp<IMemory>& dataMemory)
|
|
{
|
|
ALOGV("startRecognition() model handle %d", handle);
|
|
if (mHalInterface == 0) {
|
|
return NO_INIT;
|
|
}
|
|
|
|
struct sound_trigger_recognition_config *config =
|
|
(struct sound_trigger_recognition_config *)dataMemory->pointer();
|
|
|
|
if (config->data_offset < sizeof(struct sound_trigger_recognition_config) ||
|
|
config->data_size > (UINT_MAX - config->data_offset) ||
|
|
dataMemory->size() < config->data_offset ||
|
|
config->data_size > (dataMemory->size() - config->data_offset)) {
|
|
ALOGE("startRecognition() data_size is too big");
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
AutoMutex lock(mLock);
|
|
if (mServiceState == SOUND_TRIGGER_STATE_DISABLED) {
|
|
return INVALID_OPERATION;
|
|
}
|
|
sp<Model> model = getModel(handle);
|
|
if (model == 0) {
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
if (model->mState == Model::STATE_ACTIVE) {
|
|
return INVALID_OPERATION;
|
|
}
|
|
|
|
|
|
//TODO: get capture handle and device from audio policy service
|
|
config->capture_handle = model->mCaptureIOHandle;
|
|
config->capture_device = model->mCaptureDevice;
|
|
status_t status = mHalInterface->startRecognition(handle, config,
|
|
SoundTriggerHwService::recognitionCallback,
|
|
this);
|
|
|
|
if (status == NO_ERROR) {
|
|
model->mState = Model::STATE_ACTIVE;
|
|
model->mConfig = *config;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::Module::stopRecognition(sound_model_handle_t handle)
|
|
{
|
|
ALOGV("stopRecognition() model handle %d", handle);
|
|
if (mHalInterface == 0) {
|
|
return NO_INIT;
|
|
}
|
|
AutoMutex lock(mLock);
|
|
sp<Model> model = getModel(handle);
|
|
if (model == 0) {
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
if (model->mState != Model::STATE_ACTIVE) {
|
|
return INVALID_OPERATION;
|
|
}
|
|
mHalInterface->stopRecognition(handle);
|
|
model->mState = Model::STATE_IDLE;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t SoundTriggerHwService::Module::getModelState(sound_model_handle_t handle)
|
|
{
|
|
ALOGV("getModelState() model handle %d", handle);
|
|
if (mHalInterface == 0) {
|
|
return NO_INIT;
|
|
}
|
|
AutoMutex lock(mLock);
|
|
sp<Model> model = getModel(handle);
|
|
if (model == 0) {
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
if (model->mState != Model::STATE_ACTIVE) {
|
|
return INVALID_OPERATION;
|
|
}
|
|
|
|
return mHalInterface->getModelState(handle);
|
|
}
|
|
|
|
void SoundTriggerHwService::Module::onCallbackEvent(const sp<CallbackEvent>& event)
|
|
{
|
|
ALOGV("onCallbackEvent type %d", event->mType);
|
|
|
|
sp<IMemory> eventMemory = event->mMemory;
|
|
|
|
if (eventMemory == 0 || eventMemory->pointer() == NULL) {
|
|
return;
|
|
}
|
|
if (mModuleClients.isEmpty()) {
|
|
ALOGI("%s no clients", __func__);
|
|
return;
|
|
}
|
|
|
|
Vector< sp<ModuleClient> > clients;
|
|
|
|
switch (event->mType) {
|
|
case CallbackEvent::TYPE_RECOGNITION: {
|
|
struct sound_trigger_recognition_event *recognitionEvent =
|
|
(struct sound_trigger_recognition_event *)eventMemory->pointer();
|
|
{
|
|
AutoMutex lock(mLock);
|
|
sp<Model> model = getModel(recognitionEvent->model);
|
|
if (model == 0) {
|
|
ALOGW("%s model == 0", __func__);
|
|
return;
|
|
}
|
|
if (model->mState != Model::STATE_ACTIVE) {
|
|
ALOGV("onCallbackEvent model->mState %d != Model::STATE_ACTIVE", model->mState);
|
|
return;
|
|
}
|
|
|
|
recognitionEvent->capture_session = model->mCaptureSession;
|
|
model->mState = Model::STATE_IDLE;
|
|
clients.add(model->mModuleClient);
|
|
}
|
|
} break;
|
|
case CallbackEvent::TYPE_SOUNDMODEL: {
|
|
struct sound_trigger_model_event *soundmodelEvent =
|
|
(struct sound_trigger_model_event *)eventMemory->pointer();
|
|
{
|
|
AutoMutex lock(mLock);
|
|
sp<Model> model = getModel(soundmodelEvent->model);
|
|
if (model == 0) {
|
|
ALOGW("%s model == 0", __func__);
|
|
return;
|
|
}
|
|
clients.add(model->mModuleClient);
|
|
}
|
|
} break;
|
|
case CallbackEvent::TYPE_SERVICE_STATE: {
|
|
{
|
|
AutoMutex lock(mLock);
|
|
for (size_t i = 0; i < mModuleClients.size(); i++) {
|
|
if (mModuleClients[i] != 0) {
|
|
clients.add(mModuleClients[i]);
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
default:
|
|
LOG_ALWAYS_FATAL("onCallbackEvent unknown event type %d", event->mType);
|
|
}
|
|
|
|
for (size_t i = 0; i < clients.size(); i++) {
|
|
clients[i]->onCallbackEvent(event);
|
|
}
|
|
}
|
|
|
|
sp<SoundTriggerHwService::Model> SoundTriggerHwService::Module::getModel(
|
|
sound_model_handle_t handle)
|
|
{
|
|
sp<Model> model;
|
|
ssize_t index = mModels.indexOfKey(handle);
|
|
if (index >= 0) {
|
|
model = mModels.valueAt(index);
|
|
}
|
|
return model;
|
|
}
|
|
|
|
// Called with mServiceLock held
|
|
void SoundTriggerHwService::Module::setCaptureState_l(bool active)
|
|
{
|
|
ALOGV("Module::setCaptureState_l %d", active);
|
|
sp<SoundTriggerHwService> service;
|
|
sound_trigger_service_state_t state;
|
|
|
|
Vector< sp<IMemory> > events;
|
|
{
|
|
AutoMutex lock(mLock);
|
|
state = (active && !mDescriptor.properties.concurrent_capture) ?
|
|
SOUND_TRIGGER_STATE_DISABLED : SOUND_TRIGGER_STATE_ENABLED;
|
|
|
|
if (state == mServiceState) {
|
|
return;
|
|
}
|
|
|
|
mServiceState = state;
|
|
|
|
service = mService.promote();
|
|
if (service == 0) {
|
|
return;
|
|
}
|
|
|
|
if (state == SOUND_TRIGGER_STATE_ENABLED) {
|
|
goto exit;
|
|
}
|
|
|
|
const bool supports_stop_all =
|
|
(mHalInterface != 0) && (mHalInterface->stopAllRecognitions() != -ENOSYS);
|
|
|
|
for (size_t i = 0; i < mModels.size(); i++) {
|
|
sp<Model> model = mModels.valueAt(i);
|
|
if (model->mState == Model::STATE_ACTIVE) {
|
|
if (mHalInterface != 0 && !supports_stop_all) {
|
|
mHalInterface->stopRecognition(model->mHandle);
|
|
}
|
|
// keep model in ACTIVE state so that event is processed by onCallbackEvent()
|
|
if (model->mType == SOUND_MODEL_TYPE_KEYPHRASE) {
|
|
struct sound_trigger_phrase_recognition_event event;
|
|
memset(&event, 0, sizeof(struct sound_trigger_phrase_recognition_event));
|
|
event.num_phrases = model->mConfig.num_phrases;
|
|
for (size_t i = 0; i < event.num_phrases; i++) {
|
|
event.phrase_extras[i] = model->mConfig.phrases[i];
|
|
}
|
|
event.common.status = RECOGNITION_STATUS_ABORT;
|
|
event.common.type = model->mType;
|
|
event.common.model = model->mHandle;
|
|
event.common.data_size = 0;
|
|
sp<IMemory> eventMemory = service->prepareRecognitionEvent(&event.common);
|
|
if (eventMemory != 0) {
|
|
events.add(eventMemory);
|
|
}
|
|
} else if (model->mType == SOUND_MODEL_TYPE_GENERIC) {
|
|
struct sound_trigger_generic_recognition_event event;
|
|
memset(&event, 0, sizeof(struct sound_trigger_generic_recognition_event));
|
|
event.common.status = RECOGNITION_STATUS_ABORT;
|
|
event.common.type = model->mType;
|
|
event.common.model = model->mHandle;
|
|
event.common.data_size = 0;
|
|
sp<IMemory> eventMemory = service->prepareRecognitionEvent(&event.common);
|
|
if (eventMemory != 0) {
|
|
events.add(eventMemory);
|
|
}
|
|
} else if (model->mType == SOUND_MODEL_TYPE_UNKNOWN) {
|
|
struct sound_trigger_phrase_recognition_event event;
|
|
memset(&event, 0, sizeof(struct sound_trigger_phrase_recognition_event));
|
|
event.common.status = RECOGNITION_STATUS_ABORT;
|
|
event.common.type = model->mType;
|
|
event.common.model = model->mHandle;
|
|
event.common.data_size = 0;
|
|
sp<IMemory> eventMemory = service->prepareRecognitionEvent(&event.common);
|
|
if (eventMemory != 0) {
|
|
events.add(eventMemory);
|
|
}
|
|
} else {
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < events.size(); i++) {
|
|
sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_RECOGNITION,
|
|
events[i]);
|
|
callbackEvent->setModule(this);
|
|
service->sendCallbackEvent(callbackEvent);
|
|
}
|
|
|
|
exit:
|
|
service->sendServiceStateEvent(state, this);
|
|
}
|
|
|
|
|
|
SoundTriggerHwService::Model::Model(sound_model_handle_t handle, audio_session_t session,
|
|
audio_io_handle_t ioHandle, audio_devices_t device,
|
|
sound_trigger_sound_model_type_t type,
|
|
sp<ModuleClient>& moduleClient) :
|
|
mHandle(handle), mState(STATE_IDLE), mCaptureSession(session),
|
|
mCaptureIOHandle(ioHandle), mCaptureDevice(device), mType(type),
|
|
mModuleClient(moduleClient)
|
|
{
|
|
}
|
|
|
|
#undef LOG_TAG
|
|
#define LOG_TAG "SoundTriggerHwService::ModuleClient"
|
|
|
|
SoundTriggerHwService::ModuleClient::ModuleClient(const sp<Module>& module,
|
|
const sp<ISoundTriggerClient>& client,
|
|
const String16& opPackageName)
|
|
: mModule(module), mClient(client), mOpPackageName(opPackageName)
|
|
{
|
|
}
|
|
|
|
void SoundTriggerHwService::ModuleClient::onFirstRef()
|
|
{
|
|
sp<IBinder> binder = IInterface::asBinder(mClient);
|
|
if (binder != 0) {
|
|
binder->linkToDeath(this);
|
|
}
|
|
}
|
|
|
|
SoundTriggerHwService::ModuleClient::~ModuleClient()
|
|
{
|
|
}
|
|
|
|
status_t SoundTriggerHwService::ModuleClient::dump(int fd __unused,
|
|
const Vector<String16>& args __unused) {
|
|
String8 result;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void SoundTriggerHwService::ModuleClient::detach() {
|
|
ALOGV("detach()");
|
|
if (!captureHotwordAllowed(mOpPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
AutoMutex lock(mLock);
|
|
if (mClient != 0) {
|
|
IInterface::asBinder(mClient)->unlinkToDeath(this);
|
|
mClient.clear();
|
|
}
|
|
}
|
|
|
|
sp<Module> module = mModule.promote();
|
|
if (module == 0) {
|
|
return;
|
|
}
|
|
module->detach(this);
|
|
}
|
|
|
|
status_t SoundTriggerHwService::ModuleClient::loadSoundModel(const sp<IMemory>& modelMemory,
|
|
sound_model_handle_t *handle)
|
|
{
|
|
ALOGV("loadSoundModel() handle");
|
|
if (!captureHotwordAllowed(mOpPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return PERMISSION_DENIED;
|
|
}
|
|
if (checkIMemory(modelMemory) != NO_ERROR) {
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
sp<Module> module = mModule.promote();
|
|
if (module == 0) {
|
|
return NO_INIT;
|
|
}
|
|
return module->loadSoundModel(modelMemory, this, handle);
|
|
}
|
|
|
|
status_t SoundTriggerHwService::ModuleClient::unloadSoundModel(sound_model_handle_t handle)
|
|
{
|
|
ALOGV("unloadSoundModel() model handle %d", handle);
|
|
if (!captureHotwordAllowed(mOpPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return PERMISSION_DENIED;
|
|
}
|
|
|
|
sp<Module> module = mModule.promote();
|
|
if (module == 0) {
|
|
return NO_INIT;
|
|
}
|
|
return module->unloadSoundModel(handle);
|
|
}
|
|
|
|
status_t SoundTriggerHwService::ModuleClient::startRecognition(sound_model_handle_t handle,
|
|
const sp<IMemory>& dataMemory)
|
|
{
|
|
ALOGV("startRecognition() model handle %d", handle);
|
|
if (!captureHotwordAllowed(mOpPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return PERMISSION_DENIED;
|
|
}
|
|
if (checkIMemory(dataMemory) != NO_ERROR) {
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
sp<Module> module = mModule.promote();
|
|
if (module == 0) {
|
|
return NO_INIT;
|
|
}
|
|
return module->startRecognition(handle, dataMemory);
|
|
}
|
|
|
|
status_t SoundTriggerHwService::ModuleClient::stopRecognition(sound_model_handle_t handle)
|
|
{
|
|
ALOGV("stopRecognition() model handle %d", handle);
|
|
if (!captureHotwordAllowed(mOpPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return PERMISSION_DENIED;
|
|
}
|
|
|
|
sp<Module> module = mModule.promote();
|
|
if (module == 0) {
|
|
return NO_INIT;
|
|
}
|
|
return module->stopRecognition(handle);
|
|
}
|
|
|
|
status_t SoundTriggerHwService::ModuleClient::getModelState(sound_model_handle_t handle)
|
|
{
|
|
ALOGV("getModelState() model handle %d", handle);
|
|
if (!captureHotwordAllowed(mOpPackageName,
|
|
IPCThreadState::self()->getCallingPid(),
|
|
IPCThreadState::self()->getCallingUid())) {
|
|
return PERMISSION_DENIED;
|
|
}
|
|
|
|
sp<Module> module = mModule.promote();
|
|
if (module == 0) {
|
|
return NO_INIT;
|
|
}
|
|
return module->getModelState(handle);
|
|
}
|
|
|
|
void SoundTriggerHwService::ModuleClient::setCaptureState_l(bool active)
|
|
{
|
|
ALOGV("ModuleClient::setCaptureState_l %d", active);
|
|
sp<SoundTriggerHwService> service;
|
|
sound_trigger_service_state_t state;
|
|
|
|
sp<Module> module = mModule.promote();
|
|
if (module == 0) {
|
|
return;
|
|
}
|
|
{
|
|
AutoMutex lock(mLock);
|
|
state = (active && !module->isConcurrentCaptureAllowed()) ?
|
|
SOUND_TRIGGER_STATE_DISABLED : SOUND_TRIGGER_STATE_ENABLED;
|
|
|
|
service = module->service().promote();
|
|
if (service == 0) {
|
|
return;
|
|
}
|
|
}
|
|
service->sendServiceStateEvent(state, this);
|
|
}
|
|
|
|
void SoundTriggerHwService::ModuleClient::onCallbackEvent(const sp<CallbackEvent>& event)
|
|
{
|
|
ALOGV("ModuleClient onCallbackEvent type %d", event->mType);
|
|
|
|
sp<IMemory> eventMemory = event->mMemory;
|
|
|
|
if (eventMemory == 0 || eventMemory->pointer() == NULL) {
|
|
return;
|
|
}
|
|
|
|
sp<ISoundTriggerClient> client;
|
|
{
|
|
AutoMutex lock(mLock);
|
|
client = mClient;
|
|
}
|
|
|
|
if (client != 0) {
|
|
switch (event->mType) {
|
|
case CallbackEvent::TYPE_RECOGNITION: {
|
|
client->onRecognitionEvent(eventMemory);
|
|
} break;
|
|
case CallbackEvent::TYPE_SOUNDMODEL: {
|
|
client->onSoundModelEvent(eventMemory);
|
|
} break;
|
|
case CallbackEvent::TYPE_SERVICE_STATE: {
|
|
client->onServiceStateChange(eventMemory);
|
|
} break;
|
|
default:
|
|
LOG_ALWAYS_FATAL("onCallbackEvent unknown event type %d", event->mType);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SoundTriggerHwService::ModuleClient::binderDied(
|
|
const wp<IBinder> &who __unused) {
|
|
ALOGW("client binder died for client %p", this);
|
|
detach();
|
|
}
|
|
|
|
}; // namespace android
|