SoundTriggerHelper: Remove unused models onLoad

If some process tries to start a recognition, the STH will now check to
see if there are any other models that have been loaded and either:
 - Have a callback that's died (determined by pinging the binder)
 - Don't have a callback at all (meaning they didn't clean up after the
   last detection and didn't start it again).
If it finds such a model, it will be stopped and unloaded, freeing up
resources in the HAL.

Test: Manually load model/kill the process and make sure that it is
unloaded the next time some other model is loaded. Also made sure
running models aren't evicted.

Change-Id: Ia1139b47afe5f88102a3773d90f4f056ea3cdc92
This commit is contained in:
Chris Thornton
2017-03-24 17:30:29 -07:00
parent 2e0d532317
commit a533b9942d

View File

@@ -300,7 +300,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
// Load the model if it is not loaded.
if (!modelData.isModelLoaded()) {
// Load the model
// Before we try and load this model, we should first make sure that any other
// models that don't have an active recognition/dead callback are unloaded. Since
// there is a finite limit on the number of models that the hardware may be able to
// have loaded, we want to make sure there's room for our model.
stopAndUnloadDeadModelsLocked();
int[] handle = new int[] { INVALID_VALUE };
int status = mModule.loadSoundModel(soundModel, handle);
if (status != SoundTrigger.STATUS_OK) {
@@ -899,7 +903,29 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
/**
* Stops and unloads a sound model, and removes any reference to the model if successful.
*
* @param modelData The model data to remove.
* @param exception Optional exception to print in logcat. May be null.
*/
private void forceStopAndUnloadModel(ModelData modelData, Exception exception) {
forceStopAndUnloadModel(modelData, exception, null /* modelDataIterator */);
}
/**
* Stops and unloads a sound model, and removes any reference to the model if successful.
*
* @param modelData The model data to remove.
* @param exception Optional exception to print in logcat. May be null.
* @param modelDataIterator If this function is to be used while iterating over the
* mModelDataMap, you can provide the iterator for the current model data to be used to
* remove the modelData from the map. This avoids generating a
* ConcurrentModificationException, since this function will try and remove the model
* data from the mModelDataMap when it can successfully unload the model.
*/
private void forceStopAndUnloadModel(ModelData modelData, Exception exception,
Iterator modelDataIterator) {
if (exception != null) {
Slog.e(TAG, "forceStopAndUnloadModel", exception);
}
@@ -916,7 +942,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
Slog.d(TAG, "Unloading previously loaded dangling model " + modelData.getHandle());
if (mModule.unloadSoundModel(modelData.getHandle()) == STATUS_OK) {
// Remove the model data from existence.
mModelDataMap.remove(modelData.getModelId());
if (modelDataIterator != null) {
modelDataIterator.remove();
} else {
mModelDataMap.remove(modelData.getModelId());
}
Iterator it = mKeyphraseUuidMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
@@ -931,6 +961,23 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
private void stopAndUnloadDeadModelsLocked() {
Iterator it = mModelDataMap.entrySet().iterator();
while (it.hasNext()) {
ModelData modelData = (ModelData) ((Map.Entry) it.next()).getValue();
if (!modelData.isModelLoaded()) {
continue;
}
if (modelData.getCallback() == null
|| (modelData.getCallback().asBinder() != null
&& !modelData.getCallback().asBinder().pingBinder())) {
// No one is listening on this model, so we might as well evict it.
Slog.w(TAG, "Removing model " + modelData.getHandle() + " that has no clients");
forceStopAndUnloadModel(modelData, null /* exception */, it);
}
}
}
private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
ModelData modelData = mModelDataMap.get(modelId);
if (modelData == null) {