From a533b9942dd7a93804953d633eb842f0b7cd0ede Mon Sep 17 00:00:00 2001 From: Chris Thornton Date: Fri, 24 Mar 2017 17:30:29 -0700 Subject: [PATCH] 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 --- .../soundtrigger/SoundTriggerHelper.java | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 1aa952cd58b9e..cb44471e3c0cf 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -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) {