1 /* 2 * Copyright 2021 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 package android.hardware.soundtrigger.V2_3.cli; 17 18 import android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra; 19 import android.hardware.soundtrigger.V2_0.RecognitionMode; 20 import android.hardware.soundtrigger.V2_0.SoundModelType; 21 import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange; 22 import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; 23 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback; 24 import android.os.HidlMemoryUtil; 25 import android.os.HwBinder; 26 import android.os.RemoteException; 27 import android.os.SystemProperties; 28 29 import java.util.Scanner; 30 import java.util.concurrent.ConcurrentHashMap; 31 import java.util.concurrent.ConcurrentMap; 32 33 /** 34 * This is a quick-and-dirty sound trigger HAL console mock. 35 * 36 * It would only work on userdebug builds. 37 * 38 * When this app is started, it will initially: 39 * - Register a ISoundTriggerHw HAL with an instance name "mock". 40 * - Set a sysprop that tells SoundTriggerMiddlewareService to try to connect to the mock instance 41 * rather than the default one. 42 * - Reboot the real (default) HAL. 43 * 44 * In response to that, SoundTriggerMiddlewareService is going to connect to the mock HAL and resume 45 * normal operation. 46 * 47 * Our mock HAL will print to stdout every call it receives as well as expose a basic set of 48 * operations for sending event callbacks to the client. This allows us to simulate the frameworks 49 * behavior in response to different HAL behaviors. 50 */ 51 public class SthalCli { 52 private static SoundTriggerImpl mService; 53 private static final Scanner mScanner = new Scanner(System.in); 54 main(String[] args)55 public static void main(String[] args) { 56 try { 57 System.out.println("Registering mock STHAL"); 58 HwBinder.setTrebleTestingOverride(true); 59 mService = new SoundTriggerImpl(); 60 mService.registerAsService("mock"); 61 62 System.out.println("Rebooting STHAL"); 63 SystemProperties.set("debug.soundtrigger_middleware.use_mock_hal", "2"); 64 SystemProperties.set("sys.audio.restart.hal", "1"); 65 66 while (processCommand()) ; 67 } catch (Exception e) { 68 e.printStackTrace(); 69 } finally { 70 cleanup(); 71 } 72 } 73 cleanup()74 private static void cleanup() { 75 System.out.println("Cleaning up."); 76 SystemProperties.set("debug.soundtrigger_middleware.use_mock_hal", null); 77 HwBinder.setTrebleTestingOverride(false); 78 } 79 processCommand()80 private static boolean processCommand() { 81 String line = mScanner.nextLine(); 82 String[] tokens = line.split("\\s+"); 83 if (tokens.length < 1) { 84 return false; 85 } 86 switch (tokens[0]) { 87 case "q": 88 return false; 89 90 case "r": 91 mService.sendRecognitionEvent(Integer.parseInt(tokens[1]), 92 Integer.parseInt(tokens[2])); 93 return true; 94 95 case "p": 96 mService.sendPhraseRecognitionEvent(Integer.parseInt(tokens[1]), 97 Integer.parseInt(tokens[2])); 98 return true; 99 100 case "d": 101 mService.dumpModels(); 102 return true; 103 104 case "h": 105 System.out.print("Available commands:\n" + "h - help\n" + "q - quit\n" 106 + "r <model> <status> - send recognitionEvent\n" 107 + "p <model> <status> - send phraseRecognitionEvent\n" 108 + "d - dump models\n"); 109 110 default: 111 return true; 112 } 113 } 114 115 private static class SoundTriggerImpl extends ISoundTriggerHw.Stub { 116 static class Model { 117 final ISoundTriggerHwCallback callback; 118 final SoundModel model; 119 final PhraseSoundModel phraseModel; 120 public android.hardware.soundtrigger.V2_3.RecognitionConfig config = null; 121 Model(ISoundTriggerHwCallback callback, SoundModel model)122 Model(ISoundTriggerHwCallback callback, SoundModel model) { 123 this.callback = callback; 124 this.model = model; 125 this.phraseModel = null; 126 } 127 Model(ISoundTriggerHwCallback callback, PhraseSoundModel model)128 Model(ISoundTriggerHwCallback callback, PhraseSoundModel model) { 129 this.callback = callback; 130 this.model = null; 131 this.phraseModel = model; 132 } 133 } 134 135 private final ConcurrentMap<Integer, Model> mLoadedModels = new ConcurrentHashMap<>(); 136 private int mHandleCounter = 1; 137 dumpModels()138 public void dumpModels() { 139 mLoadedModels.forEach((handle, model) -> { 140 System.out.println("+++ Model " + handle); 141 System.out.println(" config = " + model.config); 142 android.hardware.soundtrigger.V2_3.RecognitionConfig recognitionConfig = 143 model.config; 144 if (recognitionConfig != null) { 145 System.out.println(" ACTIVE recognitionConfig = " + recognitionConfig); 146 } else { 147 System.out.println(" INACTIVE"); 148 } 149 }); 150 } 151 sendRecognitionEvent(int modelHandle, int status)152 public void sendRecognitionEvent(int modelHandle, int status) { 153 Model model = mLoadedModels.get(modelHandle); 154 if (model != null && model.config != null) { 155 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event = 156 new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent(); 157 event.header.model = modelHandle; 158 event.header.type = SoundModelType.GENERIC; 159 event.header.status = status; 160 event.header.captureSession = model.config.base.header.captureHandle; 161 event.header.captureAvailable = true; 162 event.header.audioConfig.channelMask = 16; 163 event.header.audioConfig.format = 1; 164 event.header.audioConfig.sampleRateHz = 16000; 165 event.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[0]); 166 try { 167 model.callback.recognitionCallback_2_1(event, 0); 168 } catch (RemoteException e) { 169 e.printStackTrace(); 170 } 171 model.config = null; 172 } 173 } 174 sendPhraseRecognitionEvent(int modelHandle, int status)175 public void sendPhraseRecognitionEvent(int modelHandle, int status) { 176 Model model = mLoadedModels.get(modelHandle); 177 if (model != null && model.config != null) { 178 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent 179 event = 180 new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent(); 181 event.common.header.model = modelHandle; 182 event.common.header.type = SoundModelType.KEYPHRASE; 183 event.common.header.status = status; 184 event.common.header.captureSession = model.config.base.header.captureHandle; 185 event.common.header.captureAvailable = true; 186 event.common.header.audioConfig.channelMask = 16; 187 event.common.header.audioConfig.format = 1; 188 event.common.header.audioConfig.sampleRateHz = 16000; 189 event.common.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[0]); 190 if (!model.phraseModel.phrases.isEmpty()) { 191 PhraseRecognitionExtra extra = new PhraseRecognitionExtra(); 192 extra.id = model.phraseModel.phrases.get(0).id; 193 extra.confidenceLevel = 100; 194 extra.recognitionModes = model.phraseModel.phrases.get(0).recognitionModes; 195 event.phraseExtras.add(extra); 196 } 197 try { 198 model.callback.phraseRecognitionCallback_2_1(event, 0); 199 } catch (RemoteException e) { 200 e.printStackTrace(); 201 } 202 model.config = null; 203 } 204 } 205 206 @Override loadSoundModel_2_1(SoundModel soundModel, android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie, loadSoundModel_2_1Callback _hidl_cb)207 public void loadSoundModel_2_1(SoundModel soundModel, 208 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie, 209 loadSoundModel_2_1Callback _hidl_cb) { 210 int handle = mHandleCounter++; 211 System.out.printf("loadSoundModel_2_1(soundModel=%s) -> %d%n", soundModel, handle); 212 mLoadedModels.put(handle, new Model(callback, soundModel)); 213 _hidl_cb.onValues(0, handle); 214 } 215 216 @Override loadPhraseSoundModel_2_1(PhraseSoundModel soundModel, android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie, loadPhraseSoundModel_2_1Callback _hidl_cb)217 public void loadPhraseSoundModel_2_1(PhraseSoundModel soundModel, 218 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie, 219 loadPhraseSoundModel_2_1Callback _hidl_cb) { 220 int handle = mHandleCounter++; 221 System.out.printf("loadPhraseSoundModel_2_1(soundModel=%s) -> %d%n", soundModel, 222 handle); 223 mLoadedModels.put(handle, new Model(callback, soundModel)); 224 _hidl_cb.onValues(0, handle); 225 } 226 227 @Override startRecognition_2_3(int modelHandle, android.hardware.soundtrigger.V2_3.RecognitionConfig config)228 public int startRecognition_2_3(int modelHandle, 229 android.hardware.soundtrigger.V2_3.RecognitionConfig config) { 230 System.out.printf("startRecognition_2_3(modelHandle=%d)%n", modelHandle); 231 Model model = mLoadedModels.get(modelHandle); 232 if (model != null) { 233 model.config = config; 234 } 235 return 0; 236 } 237 238 @Override getProperties_2_3(getProperties_2_3Callback _hidl_cb)239 public void getProperties_2_3(getProperties_2_3Callback _hidl_cb) { 240 System.out.println("getProperties_2_3()"); 241 android.hardware.soundtrigger.V2_3.Properties properties = 242 new android.hardware.soundtrigger.V2_3.Properties(); 243 properties.base.implementor = "Android"; 244 properties.base.description = "Mock STHAL"; 245 properties.base.maxSoundModels = 2; 246 properties.base.maxKeyPhrases = 1; 247 properties.base.recognitionModes = 248 RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER; 249 _hidl_cb.onValues(0, properties); 250 } 251 252 @Override queryParameter(int modelHandle, int modelParam, queryParameterCallback _hidl_cb)253 public void queryParameter(int modelHandle, int modelParam, 254 queryParameterCallback _hidl_cb) { 255 _hidl_cb.onValues(0, new OptionalModelParameterRange()); 256 } 257 258 @Override getModelState(int modelHandle)259 public int getModelState(int modelHandle) { 260 System.out.printf("getModelState(modelHandle=%d)%n", modelHandle); 261 return 0; 262 } 263 264 @Override unloadSoundModel(int modelHandle)265 public int unloadSoundModel(int modelHandle) { 266 System.out.printf("unloadSoundModel(modelHandle=%d)%n", modelHandle); 267 return 0; 268 } 269 270 @Override stopRecognition(int modelHandle)271 public int stopRecognition(int modelHandle) { 272 System.out.printf("stopRecognition(modelHandle=%d)%n", modelHandle); 273 Model model = mLoadedModels.get(modelHandle); 274 if (model != null) { 275 model.config = null; 276 } 277 return 0; 278 } 279 280 @Override debug(android.os.NativeHandle fd, java.util.ArrayList<String> options)281 public void debug(android.os.NativeHandle fd, java.util.ArrayList<String> options) { 282 if (!options.isEmpty()) { 283 switch (options.get(0)) { 284 case "reboot": 285 System.out.println("Received a reboot request. Exiting."); 286 cleanup(); 287 System.exit(1); 288 } 289 } 290 } 291 292 //////////////////////////////////////////////////////////////////////////////////////////// 293 // Everything below is not implemented and not expected to be called. 294 295 @Override setParameter(int modelHandle, int modelParam, int value)296 public int setParameter(int modelHandle, int modelParam, int value) { 297 throw new UnsupportedOperationException(); 298 } 299 300 @Override getParameter(int modelHandle, int modelParam, getParameterCallback _hidl_cb)301 public void getParameter(int modelHandle, int modelParam, getParameterCallback _hidl_cb) { 302 throw new UnsupportedOperationException(); 303 } 304 305 @Override startRecognition_2_1(int modelHandle, RecognitionConfig config, android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie)306 public int startRecognition_2_1(int modelHandle, RecognitionConfig config, 307 android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback, int cookie) { 308 throw new UnsupportedOperationException(); 309 } 310 311 @Override getProperties(getPropertiesCallback _hidl_cb)312 public void getProperties(getPropertiesCallback _hidl_cb) { 313 throw new UnsupportedOperationException(); 314 } 315 316 @Override loadSoundModel( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel soundModel, android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie, loadSoundModelCallback _hidl_cb)317 public void loadSoundModel( 318 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel soundModel, 319 android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie, 320 loadSoundModelCallback _hidl_cb) { 321 throw new UnsupportedOperationException(); 322 } 323 324 @Override loadPhraseSoundModel( android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel soundModel, android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie, loadPhraseSoundModelCallback _hidl_cb)325 public void loadPhraseSoundModel( 326 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel soundModel, 327 android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie, 328 loadPhraseSoundModelCallback _hidl_cb) { 329 throw new UnsupportedOperationException(); 330 } 331 332 @Override startRecognition(int modelHandle, android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config, android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie)333 public int startRecognition(int modelHandle, 334 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config, 335 android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback, int cookie) { 336 throw new UnsupportedOperationException(); 337 } 338 339 @Override stopAllRecognitions()340 public int stopAllRecognitions() { 341 throw new UnsupportedOperationException(); 342 } 343 } 344 } 345