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.Matchers.eq; 21 import static org.mockito.Mockito.reset; 22 import static org.mockito.Mockito.spy; 23 import static org.mockito.Mockito.timeout; 24 import static org.mockito.Mockito.verify; 25 26 import android.content.Context; 27 import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent; 28 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 29 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 30 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 31 import android.media.soundtrigger.SoundTriggerManager; 32 import android.os.ParcelUuid; 33 import android.os.ServiceManager; 34 import android.test.AndroidTestCase; 35 import android.test.suitebuilder.annotation.LargeTest; 36 import android.test.suitebuilder.annotation.SmallTest; 37 38 import com.android.internal.app.ISoundTriggerService; 39 40 import java.io.DataOutputStream; 41 import java.net.InetAddress; 42 import java.net.Socket; 43 import java.util.ArrayList; 44 import java.util.HashSet; 45 import java.util.Random; 46 import java.util.UUID; 47 48 import org.mockito.MockitoAnnotations; 49 50 public 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 setUp()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 tearDown()77 public void tearDown() throws Exception { 78 for (UUID modelUuid : loadedModelUuids) { 79 soundTriggerService.deleteSoundModel(new ParcelUuid(modelUuid)); 80 } 81 super.tearDown(); 82 } 83 new_sound_model()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 testUpdateGenericSoundModel()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 testDeleteGenericSoundModel()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 testStartStopGenericSoundModel()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 testTriggerGenericSoundModel()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 testFuzzGenericSoundModel()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 onGenericSoundTriggerDetected(GenericRecognitionEvent recognitionEvent)273 public void onGenericSoundTriggerDetected(GenericRecognitionEvent recognitionEvent) { 274 } 275 276 @Override onKeyphraseDetected(KeyphraseRecognitionEvent recognitionEvent)277 public void onKeyphraseDetected(KeyphraseRecognitionEvent recognitionEvent) { 278 } 279 280 @Override onError(int status)281 public void onError(int status) { 282 } 283 284 @Override onRecognitionPaused()285 public void onRecognitionPaused() { 286 } 287 288 @Override onRecognitionResumed()289 public void onRecognitionResumed() { 290 } 291 } 292 } 293