1/* 2 * Copyright (C) 2014 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 17package android.hardware.soundtrigger; 18 19import static org.mockito.Matchers.any; 20import static org.mockito.Matchers.eq; 21import static org.mockito.Mockito.reset; 22import static org.mockito.Mockito.spy; 23import static org.mockito.Mockito.timeout; 24import static org.mockito.Mockito.verify; 25 26import android.content.Context; 27import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent; 28import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 29import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 30import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 31import android.media.soundtrigger.SoundTriggerManager; 32import android.os.ParcelUuid; 33import android.os.ServiceManager; 34import android.test.AndroidTestCase; 35import android.test.suitebuilder.annotation.LargeTest; 36import android.test.suitebuilder.annotation.SmallTest; 37 38import com.android.internal.app.ISoundTriggerService; 39 40import java.io.DataOutputStream; 41import java.net.InetAddress; 42import java.net.Socket; 43import java.util.ArrayList; 44import java.util.HashSet; 45import java.util.Random; 46import java.util.UUID; 47 48import org.mockito.MockitoAnnotations; 49 50public class GenericSoundModelTest extends AndroidTestCase { 51 static final int MSG_DETECTION_ERROR = -1; 52 static final int MSG_DETECTION_RESUME = 0; 53 static final int MSG_DETECTION_PAUSE = 1; 54 static final int MSG_KEYPHRASE_TRIGGER = 2; 55 static final int MSG_GENERIC_TRIGGER = 4; 56 57 private Random random = new Random(); 58 private HashSet<UUID> loadedModelUuids; 59 private ISoundTriggerService soundTriggerService; 60 private SoundTriggerManager soundTriggerManager; 61 62 @Override 63 public void setUp() throws Exception { 64 super.setUp(); 65 MockitoAnnotations.initMocks(this); 66 67 Context context = getContext(); 68 soundTriggerService = ISoundTriggerService.Stub.asInterface( 69 ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE)); 70 soundTriggerManager = (SoundTriggerManager) context.getSystemService( 71 Context.SOUND_TRIGGER_SERVICE); 72 73 loadedModelUuids = new HashSet<UUID>(); 74 } 75 76 @Override 77 public void tearDown() throws Exception { 78 for (UUID modelUuid : loadedModelUuids) { 79 soundTriggerService.deleteSoundModel(new ParcelUuid(modelUuid)); 80 } 81 super.tearDown(); 82 } 83 84 GenericSoundModel new_sound_model() { 85 // Create sound model 86 byte[] data = new byte[1024]; 87 random.nextBytes(data); 88 UUID modelUuid = UUID.randomUUID(); 89 UUID mVendorUuid = UUID.randomUUID(); 90 return new GenericSoundModel(modelUuid, mVendorUuid, data); 91 } 92 93 @SmallTest 94 public void testUpdateGenericSoundModel() throws Exception { 95 GenericSoundModel model = new_sound_model(); 96 97 // Update sound model 98 soundTriggerService.updateSoundModel(model); 99 loadedModelUuids.add(model.uuid); 100 101 // Confirm it was updated 102 GenericSoundModel returnedModel = 103 soundTriggerService.getSoundModel(new ParcelUuid(model.uuid)); 104 assertEquals(model, returnedModel); 105 } 106 107 @SmallTest 108 public void testDeleteGenericSoundModel() throws Exception { 109 GenericSoundModel model = new_sound_model(); 110 111 // Update sound model 112 soundTriggerService.updateSoundModel(model); 113 loadedModelUuids.add(model.uuid); 114 115 // Delete sound model 116 soundTriggerService.deleteSoundModel(new ParcelUuid(model.uuid)); 117 loadedModelUuids.remove(model.uuid); 118 119 // Confirm it was deleted 120 GenericSoundModel returnedModel = 121 soundTriggerService.getSoundModel(new ParcelUuid(model.uuid)); 122 assertEquals(null, returnedModel); 123 } 124 125 @LargeTest 126 public void testStartStopGenericSoundModel() throws Exception { 127 GenericSoundModel model = new_sound_model(); 128 129 boolean captureTriggerAudio = true; 130 boolean allowMultipleTriggers = true; 131 RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, 132 null, null); 133 TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); 134 135 // Update and start sound model recognition 136 soundTriggerService.updateSoundModel(model); 137 loadedModelUuids.add(model.uuid); 138 int r = soundTriggerService.startRecognition(new ParcelUuid(model.uuid), spyCallback, 139 config); 140 assertEquals("Could Not Start Recognition with code: " + r, 141 android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); 142 143 // Stop recognition 144 r = soundTriggerService.stopRecognition(new ParcelUuid(model.uuid), spyCallback); 145 assertEquals("Could Not Stop Recognition with code: " + r, 146 android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); 147 } 148 149 @LargeTest 150 public void testTriggerGenericSoundModel() throws Exception { 151 GenericSoundModel model = new_sound_model(); 152 153 boolean captureTriggerAudio = true; 154 boolean allowMultipleTriggers = true; 155 RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, 156 null, null); 157 TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); 158 159 // Update and start sound model 160 soundTriggerService.updateSoundModel(model); 161 loadedModelUuids.add(model.uuid); 162 soundTriggerService.startRecognition(new ParcelUuid(model.uuid), spyCallback, config); 163 164 // Send trigger to stub HAL 165 Socket socket = new Socket(InetAddress.getLocalHost(), 14035); 166 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); 167 out.writeBytes("trig " + model.uuid.toString() + "\r\n"); 168 out.flush(); 169 socket.close(); 170 171 // Verify trigger was received 172 verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); 173 } 174 175 /** 176 * Tests a more complicated pattern of loading, unloading, triggering, starting and stopping 177 * recognition. Intended to find unexpected errors that occur in unexpected states. 178 */ 179 @LargeTest 180 public void testFuzzGenericSoundModel() throws Exception { 181 int numModels = 2; 182 183 final int STATUS_UNLOADED = 0; 184 final int STATUS_LOADED = 1; 185 final int STATUS_STARTED = 2; 186 187 class ModelInfo { 188 int status; 189 GenericSoundModel model; 190 191 public ModelInfo(GenericSoundModel model, int status) { 192 this.status = status; 193 this.model = model; 194 } 195 } 196 197 Random predictableRandom = new Random(100); 198 199 ArrayList modelInfos = new ArrayList<ModelInfo>(); 200 for(int i=0; i<numModels; i++) { 201 // Create sound model 202 byte[] data = new byte[1024]; 203 predictableRandom.nextBytes(data); 204 UUID modelUuid = UUID.randomUUID(); 205 UUID mVendorUuid = UUID.randomUUID(); 206 GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data); 207 ModelInfo modelInfo = new ModelInfo(model, STATUS_UNLOADED); 208 modelInfos.add(modelInfo); 209 } 210 211 boolean captureTriggerAudio = true; 212 boolean allowMultipleTriggers = true; 213 RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, 214 null, null); 215 TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); 216 217 218 int numOperationsToRun = 100; 219 for(int i=0; i<numOperationsToRun; i++) { 220 // Select a random model 221 int modelInfoIndex = predictableRandom.nextInt(modelInfos.size()); 222 ModelInfo modelInfo = (ModelInfo) modelInfos.get(modelInfoIndex); 223 224 // Perform a random operation 225 int operation = predictableRandom.nextInt(5); 226 227 if (operation == 0 && modelInfo.status == STATUS_UNLOADED) { 228 // Update and start sound model 229 soundTriggerService.updateSoundModel(modelInfo.model); 230 loadedModelUuids.add(modelInfo.model.uuid); 231 modelInfo.status = STATUS_LOADED; 232 } else if (operation == 1 && modelInfo.status == STATUS_LOADED) { 233 // Start the sound model 234 int r = soundTriggerService.startRecognition(new ParcelUuid(modelInfo.model.uuid), 235 spyCallback, config); 236 assertEquals("Could Not Start Recognition with code: " + r, 237 android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); 238 modelInfo.status = STATUS_STARTED; 239 } else if (operation == 2 && modelInfo.status == STATUS_STARTED) { 240 // Send trigger to stub HAL 241 Socket socket = new Socket(InetAddress.getLocalHost(), 14035); 242 DataOutputStream out = new DataOutputStream(socket.getOutputStream()); 243 out.writeBytes("trig " + modelInfo.model.uuid + "\r\n"); 244 out.flush(); 245 socket.close(); 246 247 // Verify trigger was received 248 verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); 249 reset(spyCallback); 250 } else if (operation == 3 && modelInfo.status == STATUS_STARTED) { 251 // Stop recognition 252 int r = soundTriggerService.stopRecognition(new ParcelUuid(modelInfo.model.uuid), 253 spyCallback); 254 assertEquals("Could Not Stop Recognition with code: " + r, 255 android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); 256 modelInfo.status = STATUS_LOADED; 257 } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) { 258 // Delete sound model 259 soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.uuid)); 260 loadedModelUuids.remove(modelInfo.model.uuid); 261 262 // Confirm it was deleted 263 GenericSoundModel returnedModel = 264 soundTriggerService.getSoundModel(new ParcelUuid(modelInfo.model.uuid)); 265 assertEquals(null, returnedModel); 266 modelInfo.status = STATUS_UNLOADED; 267 } 268 } 269 } 270 271 public class TestRecognitionStatusCallback extends IRecognitionStatusCallback.Stub { 272 @Override 273 public void onGenericSoundTriggerDetected(GenericRecognitionEvent recognitionEvent) { 274 } 275 276 @Override 277 public void onKeyphraseDetected(KeyphraseRecognitionEvent recognitionEvent) { 278 } 279 280 @Override 281 public void onError(int status) { 282 } 283 284 @Override 285 public void onRecognitionPaused() { 286 } 287 288 @Override 289 public void onRecognitionResumed() { 290 } 291 } 292} 293