| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.hardware.soundtrigger; |
| |
| import static org.mockito.Matchers.any; |
| import static org.mockito.Matchers.eq; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.verify; |
| |
| import android.content.Context; |
| import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent; |
| import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; |
| import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; |
| import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; |
| import android.media.soundtrigger.SoundTriggerManager; |
| import android.os.ParcelUuid; |
| import android.os.ServiceManager; |
| import android.test.AndroidTestCase; |
| import android.test.suitebuilder.annotation.LargeTest; |
| import android.test.suitebuilder.annotation.SmallTest; |
| |
| import com.android.internal.app.ISoundTriggerService; |
| |
| import java.io.DataOutputStream; |
| import java.net.InetAddress; |
| import java.net.Socket; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Random; |
| import java.util.UUID; |
| |
| import org.mockito.MockitoAnnotations; |
| |
| public class GenericSoundModelTest extends AndroidTestCase { |
| static final int MSG_DETECTION_ERROR = -1; |
| static final int MSG_DETECTION_RESUME = 0; |
| static final int MSG_DETECTION_PAUSE = 1; |
| static final int MSG_KEYPHRASE_TRIGGER = 2; |
| static final int MSG_GENERIC_TRIGGER = 4; |
| |
| private Random random = new Random(); |
| private HashSet<UUID> loadedModelUuids; |
| private ISoundTriggerService soundTriggerService; |
| private SoundTriggerManager soundTriggerManager; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| MockitoAnnotations.initMocks(this); |
| |
| Context context = getContext(); |
| soundTriggerService = ISoundTriggerService.Stub.asInterface( |
| ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE)); |
| soundTriggerManager = (SoundTriggerManager) context.getSystemService( |
| Context.SOUND_TRIGGER_SERVICE); |
| |
| loadedModelUuids = new HashSet<UUID>(); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| for (UUID modelUuid : loadedModelUuids) { |
| soundTriggerService.deleteSoundModel(new ParcelUuid(modelUuid)); |
| } |
| super.tearDown(); |
| } |
| |
| GenericSoundModel new_sound_model() { |
| // Create sound model |
| byte[] data = new byte[1024]; |
| random.nextBytes(data); |
| UUID modelUuid = UUID.randomUUID(); |
| UUID mVendorUuid = UUID.randomUUID(); |
| return new GenericSoundModel(modelUuid, mVendorUuid, data); |
| } |
| |
| @SmallTest |
| public void testUpdateGenericSoundModel() throws Exception { |
| GenericSoundModel model = new_sound_model(); |
| |
| // Update sound model |
| soundTriggerService.updateSoundModel(model); |
| loadedModelUuids.add(model.uuid); |
| |
| // Confirm it was updated |
| GenericSoundModel returnedModel = |
| soundTriggerService.getSoundModel(new ParcelUuid(model.uuid)); |
| assertEquals(model, returnedModel); |
| } |
| |
| @SmallTest |
| public void testDeleteGenericSoundModel() throws Exception { |
| GenericSoundModel model = new_sound_model(); |
| |
| // Update sound model |
| soundTriggerService.updateSoundModel(model); |
| loadedModelUuids.add(model.uuid); |
| |
| // Delete sound model |
| soundTriggerService.deleteSoundModel(new ParcelUuid(model.uuid)); |
| loadedModelUuids.remove(model.uuid); |
| |
| // Confirm it was deleted |
| GenericSoundModel returnedModel = |
| soundTriggerService.getSoundModel(new ParcelUuid(model.uuid)); |
| assertEquals(null, returnedModel); |
| } |
| |
| @LargeTest |
| public void testStartStopGenericSoundModel() throws Exception { |
| GenericSoundModel model = new_sound_model(); |
| |
| boolean captureTriggerAudio = true; |
| boolean allowMultipleTriggers = true; |
| RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, |
| null, null); |
| TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); |
| |
| // Update and start sound model recognition |
| soundTriggerService.updateSoundModel(model); |
| loadedModelUuids.add(model.uuid); |
| int r = soundTriggerService.startRecognition(new ParcelUuid(model.uuid), spyCallback, |
| config); |
| assertEquals("Could Not Start Recognition with code: " + r, |
| android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); |
| |
| // Stop recognition |
| r = soundTriggerService.stopRecognition(new ParcelUuid(model.uuid), spyCallback); |
| assertEquals("Could Not Stop Recognition with code: " + r, |
| android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); |
| } |
| |
| @LargeTest |
| public void testTriggerGenericSoundModel() throws Exception { |
| GenericSoundModel model = new_sound_model(); |
| |
| boolean captureTriggerAudio = true; |
| boolean allowMultipleTriggers = true; |
| RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, |
| null, null); |
| TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); |
| |
| // Update and start sound model |
| soundTriggerService.updateSoundModel(model); |
| loadedModelUuids.add(model.uuid); |
| soundTriggerService.startRecognition(new ParcelUuid(model.uuid), spyCallback, config); |
| |
| // Send trigger to stub HAL |
| Socket socket = new Socket(InetAddress.getLocalHost(), 14035); |
| DataOutputStream out = new DataOutputStream(socket.getOutputStream()); |
| out.writeBytes("trig " + model.uuid.toString() + "\r\n"); |
| out.flush(); |
| socket.close(); |
| |
| // Verify trigger was received |
| verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); |
| } |
| |
| /** |
| * Tests a more complicated pattern of loading, unloading, triggering, starting and stopping |
| * recognition. Intended to find unexpected errors that occur in unexpected states. |
| */ |
| @LargeTest |
| public void testFuzzGenericSoundModel() throws Exception { |
| int numModels = 2; |
| |
| final int STATUS_UNLOADED = 0; |
| final int STATUS_LOADED = 1; |
| final int STATUS_STARTED = 2; |
| |
| class ModelInfo { |
| int status; |
| GenericSoundModel model; |
| |
| public ModelInfo(GenericSoundModel model, int status) { |
| this.status = status; |
| this.model = model; |
| } |
| } |
| |
| Random predictableRandom = new Random(100); |
| |
| ArrayList modelInfos = new ArrayList<ModelInfo>(); |
| for(int i=0; i<numModels; i++) { |
| // Create sound model |
| byte[] data = new byte[1024]; |
| predictableRandom.nextBytes(data); |
| UUID modelUuid = UUID.randomUUID(); |
| UUID mVendorUuid = UUID.randomUUID(); |
| GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data); |
| ModelInfo modelInfo = new ModelInfo(model, STATUS_UNLOADED); |
| modelInfos.add(modelInfo); |
| } |
| |
| boolean captureTriggerAudio = true; |
| boolean allowMultipleTriggers = true; |
| RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, |
| null, null); |
| TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); |
| |
| |
| int numOperationsToRun = 100; |
| for(int i=0; i<numOperationsToRun; i++) { |
| // Select a random model |
| int modelInfoIndex = predictableRandom.nextInt(modelInfos.size()); |
| ModelInfo modelInfo = (ModelInfo) modelInfos.get(modelInfoIndex); |
| |
| // Perform a random operation |
| int operation = predictableRandom.nextInt(5); |
| |
| if (operation == 0 && modelInfo.status == STATUS_UNLOADED) { |
| // Update and start sound model |
| soundTriggerService.updateSoundModel(modelInfo.model); |
| loadedModelUuids.add(modelInfo.model.uuid); |
| modelInfo.status = STATUS_LOADED; |
| } else if (operation == 1 && modelInfo.status == STATUS_LOADED) { |
| // Start the sound model |
| int r = soundTriggerService.startRecognition(new ParcelUuid(modelInfo.model.uuid), |
| spyCallback, config); |
| assertEquals("Could Not Start Recognition with code: " + r, |
| android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); |
| modelInfo.status = STATUS_STARTED; |
| } else if (operation == 2 && modelInfo.status == STATUS_STARTED) { |
| // Send trigger to stub HAL |
| Socket socket = new Socket(InetAddress.getLocalHost(), 14035); |
| DataOutputStream out = new DataOutputStream(socket.getOutputStream()); |
| out.writeBytes("trig " + modelInfo.model.uuid + "\r\n"); |
| out.flush(); |
| socket.close(); |
| |
| // Verify trigger was received |
| verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); |
| reset(spyCallback); |
| } else if (operation == 3 && modelInfo.status == STATUS_STARTED) { |
| // Stop recognition |
| int r = soundTriggerService.stopRecognition(new ParcelUuid(modelInfo.model.uuid), |
| spyCallback); |
| assertEquals("Could Not Stop Recognition with code: " + r, |
| android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); |
| modelInfo.status = STATUS_LOADED; |
| } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) { |
| // Delete sound model |
| soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.uuid)); |
| loadedModelUuids.remove(modelInfo.model.uuid); |
| |
| // Confirm it was deleted |
| GenericSoundModel returnedModel = |
| soundTriggerService.getSoundModel(new ParcelUuid(modelInfo.model.uuid)); |
| assertEquals(null, returnedModel); |
| modelInfo.status = STATUS_UNLOADED; |
| } |
| } |
| } |
| |
| public class TestRecognitionStatusCallback extends IRecognitionStatusCallback.Stub { |
| @Override |
| public void onGenericSoundTriggerDetected(GenericRecognitionEvent recognitionEvent) { |
| } |
| |
| @Override |
| public void onKeyphraseDetected(KeyphraseRecognitionEvent recognitionEvent) { |
| } |
| |
| @Override |
| public void onError(int status) { |
| } |
| |
| @Override |
| public void onRecognitionPaused() { |
| } |
| |
| @Override |
| public void onRecognitionResumed() { |
| } |
| } |
| } |