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