1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.soundtrigger_middleware; 18 19 import android.annotation.NonNull; 20 import android.media.soundtrigger.ModelParameterRange; 21 import android.media.soundtrigger.PhraseSoundModel; 22 import android.media.soundtrigger.Properties; 23 import android.media.soundtrigger.RecognitionConfig; 24 import android.media.soundtrigger.SoundModel; 25 import android.media.soundtrigger.Status; 26 import android.os.IBinder; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 /** 32 * This wrapper prevents a models with the same UUID from being loaded concurrently. This is used to 33 * protect STHAL implementations, which don't support concurrent loads of the same model. We reject 34 * the duplicate load with {@link Status#RESOURCE_CONTENTION}. 35 */ 36 public class SoundTriggerDuplicateModelHandler implements ISoundTriggerHal { 37 private final @NonNull ISoundTriggerHal mDelegate; 38 39 private GlobalCallback mGlobalCallback; 40 // There are rarely more than two models loaded. 41 private final List<ModelData> mModelList = new ArrayList<>(); 42 43 private static final class ModelData { ModelData(int modelId, String uuid)44 ModelData(int modelId, String uuid) { 45 mModelId = modelId; 46 mUuid = uuid; 47 } 48 getModelId()49 int getModelId() { 50 return mModelId; 51 } 52 getUuid()53 String getUuid() { 54 return mUuid; 55 } 56 getWasContended()57 boolean getWasContended() { 58 return mWasContended; 59 } 60 setWasContended()61 void setWasContended() { 62 mWasContended = true; 63 } 64 65 private int mModelId; 66 private String mUuid; 67 private boolean mWasContended = false; 68 } 69 SoundTriggerDuplicateModelHandler(@onNull ISoundTriggerHal delegate)70 public SoundTriggerDuplicateModelHandler(@NonNull ISoundTriggerHal delegate) { 71 mDelegate = delegate; 72 } 73 74 @Override reboot()75 public void reboot() { 76 mDelegate.reboot(); 77 } 78 79 @Override detach()80 public void detach() { 81 mDelegate.detach(); 82 } 83 84 @Override getProperties()85 public Properties getProperties() { 86 return mDelegate.getProperties(); 87 } 88 89 @Override registerCallback(GlobalCallback callback)90 public void registerCallback(GlobalCallback callback) { 91 mGlobalCallback = callback; 92 mDelegate.registerCallback(mGlobalCallback); 93 } 94 95 @Override loadSoundModel(SoundModel soundModel, ModelCallback callback)96 public int loadSoundModel(SoundModel soundModel, ModelCallback callback) { 97 synchronized (this) { 98 checkDuplicateModelUuid(soundModel.uuid); 99 var result = mDelegate.loadSoundModel(soundModel, callback); 100 mModelList.add(new ModelData(result, soundModel.uuid)); 101 return result; 102 } 103 } 104 105 @Override loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback)106 public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) { 107 synchronized (this) { 108 checkDuplicateModelUuid(soundModel.common.uuid); 109 var result = mDelegate.loadPhraseSoundModel(soundModel, callback); 110 mModelList.add(new ModelData(result, soundModel.common.uuid)); 111 return result; 112 } 113 } 114 115 @Override unloadSoundModel(int modelHandle)116 public void unloadSoundModel(int modelHandle) { 117 mDelegate.unloadSoundModel(modelHandle); 118 for (int i = 0; i < mModelList.size(); i++) { 119 if (mModelList.get(i).getModelId() == modelHandle) { 120 var modelData = mModelList.remove(i); 121 if (modelData.getWasContended()) { 122 mGlobalCallback.onResourcesAvailable(); 123 } 124 // Model ID is unique 125 return; 126 } 127 } 128 } 129 130 // Uninteresting delegation calls to follow. 131 @Override stopRecognition(int modelHandle)132 public void stopRecognition(int modelHandle) { 133 mDelegate.stopRecognition(modelHandle); 134 } 135 136 @Override startRecognition( int modelHandle, int deviceHandle, int ioHandle, RecognitionConfig config)137 public void startRecognition( 138 int modelHandle, int deviceHandle, int ioHandle, RecognitionConfig config) { 139 mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config); 140 } 141 142 @Override forceRecognitionEvent(int modelHandle)143 public void forceRecognitionEvent(int modelHandle) { 144 mDelegate.forceRecognitionEvent(modelHandle); 145 } 146 147 @Override getModelParameter(int modelHandle, int param)148 public int getModelParameter(int modelHandle, int param) { 149 return mDelegate.getModelParameter(modelHandle, param); 150 } 151 152 @Override setModelParameter(int modelHandle, int param, int value)153 public void setModelParameter(int modelHandle, int param, int value) { 154 mDelegate.setModelParameter(modelHandle, param, value); 155 } 156 157 @Override queryParameter(int modelHandle, int param)158 public ModelParameterRange queryParameter(int modelHandle, int param) { 159 return mDelegate.queryParameter(modelHandle, param); 160 } 161 162 @Override linkToDeath(IBinder.DeathRecipient recipient)163 public void linkToDeath(IBinder.DeathRecipient recipient) { 164 mDelegate.linkToDeath(recipient); 165 } 166 167 @Override unlinkToDeath(IBinder.DeathRecipient recipient)168 public void unlinkToDeath(IBinder.DeathRecipient recipient) { 169 mDelegate.unlinkToDeath(recipient); 170 } 171 172 @Override interfaceDescriptor()173 public String interfaceDescriptor() { 174 return mDelegate.interfaceDescriptor(); 175 } 176 177 @Override flushCallbacks()178 public void flushCallbacks() { 179 mDelegate.flushCallbacks(); 180 } 181 182 @Override clientAttached(IBinder binder)183 public void clientAttached(IBinder binder) { 184 mDelegate.clientAttached(binder); 185 } 186 187 @Override clientDetached(IBinder binder)188 public void clientDetached(IBinder binder) { 189 mDelegate.clientDetached(binder); 190 } 191 192 /** 193 * Helper for handling duplicate model. If there is a load attempt for a model with a UUID which 194 * is already loaded: 1) Reject with {@link Status.RESOURCE_CONTENTION} 2) Mark the already 195 * loaded model as contended, as we need to dispatch a resource available callback following the 196 * original model being unloaded. 197 */ checkDuplicateModelUuid(String uuid)198 private void checkDuplicateModelUuid(String uuid) { 199 var model = mModelList.stream().filter(x -> x.getUuid().equals(uuid)).findFirst(); 200 if (model.isPresent()) { 201 model.get().setWasContended(); 202 throw new RecoverableException(Status.RESOURCE_CONTENTION); 203 } 204 } 205 } 206