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