• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.nativemidi.cts;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.media.midi.MidiDevice;
22 import android.media.midi.MidiDeviceInfo;
23 import android.media.midi.MidiManager;
24 import android.util.Log;
25 
26 import androidx.test.InstrumentationRegistry;
27 import androidx.test.runner.AndroidJUnit4;
28 
29 import com.android.midi.MidiEchoTestService;
30 
31 import org.junit.After;
32 import org.junit.Assert;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.io.IOException;
38 import java.util.Random;
39 
40 /*
41  * Test Class
42  */
43 @RunWith(AndroidJUnit4.class)
44 public class NativeMidiEchoTest {
45     private static final String TAG = "NativeMidiEchoTest";
46 
47     private static final long NANOS_PER_MSEC = 1000L * 1000L;
48 
49     // This number seems excessively large and it is not clear if there is a linear
50     // relationship between the number of messages sent and the time required to send them
51     private static final int TIMEOUT_PER_MESSAGE_MS = 10;
52 
53     // This timeout value is very generous.
54     private static final int TIMEOUT_OPEN_MSEC = 2000; // arbitrary
55 
56     private Context mContext = InstrumentationRegistry.getContext();
57     private MidiManager mMidiManager;
58 
59     private MidiDevice mEchoDevice;
60 
61     private Random mRandom = new Random(1972941337);
62 
63     // (Native code) attributes associated with a test/EchoServer instance.
64     private long mTestContext;
65 
66     static {
67         System.loadLibrary("nativemidi_jni");
68     }
69 
70     /*
71      * Helpers
72      */
hasMidiSupport()73     private boolean hasMidiSupport() {
74         PackageManager pm = mContext.getPackageManager();
75         return pm.hasSystemFeature(PackageManager.FEATURE_MIDI);
76     }
77 
hasLibAMidi()78     public static boolean hasLibAMidi() {
79         try {
80             System.loadLibrary("amidi");
81         } catch (UnsatisfiedLinkError ex) {
82             Log.e(TAG, "libamidi.so not found.");
83             return false;
84         }
85         return true;
86     }
87 
generateRandomMessage(int len)88     private byte[] generateRandomMessage(int len) {
89         byte[] buffer = new byte[len];
90         for(int index = 0; index < len; index++) {
91             buffer[index] = (byte)(mRandom.nextInt() & 0xFF);
92         }
93         return buffer;
94     }
95 
generateRandomBufers(byte[][] buffers, long timestamps[], int numMessages)96     private void generateRandomBufers(byte[][] buffers, long timestamps[], int numMessages) {
97         int messageLen;
98         int maxMessageLen = 128;
99         for(int buffIndex = 0; buffIndex < numMessages; buffIndex++) {
100             messageLen = (int)(mRandom.nextFloat() * (maxMessageLen-1)) + 1;
101             buffers[buffIndex] = generateRandomMessage(messageLen);
102             timestamps[buffIndex] = Math.abs(mRandom.nextLong());
103         }
104     }
105 
compareMessages(byte[] buffer, long timestamp, NativeMidiMessage nativeMsg)106     private void compareMessages(byte[] buffer, long timestamp, NativeMidiMessage nativeMsg) {
107         Assert.assertEquals("byte count of message", buffer.length, nativeMsg.len);
108         Assert.assertEquals("timestamp in message", timestamp, nativeMsg.timestamp);
109 
110         for (int index = 0; index < buffer.length; index++) {
111             Assert.assertEquals("message byte[" + index + "]", buffer[index] & 0x0FF,
112                     nativeMsg.buffer[index] & 0x0FF);
113         }
114     }
115 
116     /*
117      * Echo Server
118      */
119     // Listens for an asynchronous device open and notifies waiting foreground
120     // test.
121     class MyTestOpenCallback implements MidiManager.OnDeviceOpenedListener {
122         private volatile MidiDevice mDevice;
123 
124         @Override
onDeviceOpened(MidiDevice device)125         public synchronized void onDeviceOpened(MidiDevice device) {
126             mDevice = device;
127             notifyAll();
128         }
129 
waitForOpen(int msec)130         public synchronized MidiDevice waitForOpen(int msec)
131               throws InterruptedException {
132             long deadline = System.currentTimeMillis() + msec;
133             long timeRemaining = msec;
134             while (mDevice == null && timeRemaining > 0) {
135                 wait(timeRemaining);
136                 timeRemaining = deadline - System.currentTimeMillis();
137             }
138             return mDevice;
139         }
140     }
141 
setUpEchoServer()142      protected void setUpEchoServer() throws Exception {
143         MidiDeviceInfo echoInfo = MidiEchoTestService.findEchoDevice(mContext);
144 
145         // Open device.
146         MyTestOpenCallback callback = new MyTestOpenCallback();
147         mMidiManager.openDevice(echoInfo, callback, null);
148         mEchoDevice = callback.waitForOpen(TIMEOUT_OPEN_MSEC);
149         Assert.assertNotNull(
150                 "could not open " + MidiEchoTestService.getEchoServerName(), mEchoDevice);
151 
152         // Query echo service directly to see if it is getting status updates.
153         MidiEchoTestService echoService = MidiEchoTestService.getInstance();
154 
155         mTestContext = allocTestContext();
156         Assert.assertTrue("couldn't allocate test context.", mTestContext != 0);
157 
158         // Open Device
159         int result = openNativeMidiDevice(mTestContext, mEchoDevice);
160         Assert.assertEquals("Bad open native MIDI device", 0, result);
161 
162         // Open Input
163         result = startWritingMidi(mTestContext, 0/*mPortNumber*/);
164         Assert.assertEquals("Bad start writing (native) MIDI", 0, result);
165 
166         // Open Output
167         result = startReadingMidi(mTestContext, 0/*mPortNumber*/);
168         Assert.assertEquals("Bad start Reading (native) MIDI", 0, result);
169     }
170 
tearDownEchoServer()171     protected void tearDownEchoServer() throws IOException {
172         // Query echo service directly to see if it is getting status updates.
173         MidiEchoTestService echoService = MidiEchoTestService.getInstance();
174 
175         int result;
176 
177         // Stop inputs
178         result = stopReadingMidi(mTestContext);
179         Assert.assertEquals("Bad stop reading (native) MIDI", 0, result);
180 
181         // Stop outputs
182         result = stopWritingMidi(mTestContext);
183         Assert.assertEquals("Bad stop writing (native) MIDI", 0, result);
184 
185         // Close Device
186         result = closeNativeMidiDevice(mTestContext);
187         Assert.assertEquals("Bad close native MIDI device", 0, result);
188 
189         freeTestContext(mTestContext);
190         mTestContext = 0;
191 
192         mEchoDevice.close();
193     }
194 
195     // Search through the available devices for the ECHO loop-back device.
196 //    protected MidiDeviceInfo findEchoDevice() {
197 //        MidiDeviceInfo[] infos = mMidiManager.getDevices();
198 //        MidiDeviceInfo echoInfo = null;
199 //        for (MidiDeviceInfo info : infos) {
200 //            Bundle properties = info.getProperties();
201 //            String manufacturer = (String) properties.get(
202 //                    MidiDeviceInfo.PROPERTY_MANUFACTURER);
203 //
204 //            if (TEST_MANUFACTURER.equals(manufacturer)) {
205 //                String product = (String) properties.get(
206 //                        MidiDeviceInfo.PROPERTY_PRODUCT);
207 //                if (MidiEchoTestService.getEchoServerName().equals(product)) {
208 //                    echoInfo = info;
209 //                    break;
210 //                }
211 //            }
212 //        }
213 //        Assert.assertNotNull("could not find " + MidiEchoTestService.getEchoServerName(), echoInfo);
214 //        return echoInfo;
215 //    }
216 //
217     @Before
setUp()218     public void setUp() throws Exception {
219         if (!hasMidiSupport()) {
220             return; // Not supported so don't test it.
221         }
222 
223         mMidiManager = (MidiManager)mContext.getSystemService(Context.MIDI_SERVICE);
224         Assert.assertNotNull("Could not get the MidiManger.", mMidiManager);
225 
226         setUpEchoServer();
227     }
228 
229     @After
tearDown()230     public void tearDown() throws Exception {
231         if (!hasMidiSupport()) {
232             return; // Not supported so don't test it.
233         }
234         tearDownEchoServer();
235 
236         mMidiManager = null;
237     }
238 
239     @Test
test_A_MidiManager()240     public void test_A_MidiManager() throws Exception {
241         if (!hasMidiSupport()) {
242             return;
243         }
244         Assert.assertNotNull("MidiManager not supported.", mMidiManager);
245 
246         // There should be at least one device for the Echo server.
247         MidiDeviceInfo[] infos = mMidiManager.getDevices();
248         Assert.assertNotNull("device list was null", infos);
249         Assert.assertTrue("device list was empty", infos.length >= 1);
250     }
251 
252     @Test
test_AA_LibAMidiExists()253     public void test_AA_LibAMidiExists() throws Exception {
254         if (!hasMidiSupport()) {
255             return;
256         }
257         Assert.assertTrue("libamidi.so not found.", hasLibAMidi());
258     }
259 
260     @Test
test_B_SendData()261     public void test_B_SendData() throws Exception {
262         if (!hasMidiSupport()) {
263             return; // Nothing to test
264         }
265 
266         Assert.assertEquals("Didn't start with 0 sends", 0, getNumSends(mTestContext));
267         Assert.assertEquals("Didn't start with 0 bytes sent", 0, getNumBytesSent(mTestContext));
268 
269         final byte[] buffer = {
270                 (byte) 0x93, 0x47, 0x52
271         };
272         long timestamp = 0x0123765489ABFEDCL;
273         writeMidi(mTestContext, buffer, 0, buffer.length);
274 
275         Assert.assertEquals("Didn't get right number of bytes sent",
276                 buffer.length, getNumBytesSent(mTestContext));
277     }
278 
279     @Test
test_C_EchoSmallMessage()280     public void test_C_EchoSmallMessage() throws Exception {
281         if (!hasMidiSupport()) {
282             return;
283         }
284         final byte[] buffer = {
285                 (byte) 0x93, 0x47, 0x52
286         };
287         long timestamp = 0x0123765489ABFEDCL;
288 
289         writeMidiWithTimestamp(mTestContext, buffer, 0, 0, timestamp); // should be a NOOP
290         writeMidiWithTimestamp(mTestContext, buffer, 0, buffer.length, timestamp);
291         writeMidiWithTimestamp(mTestContext, buffer, 0, 0, timestamp); // should be a NOOP
292 
293         // Wait for message to pass quickly through echo service.
294         final int numMessages = 1;
295         final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
296         Thread.sleep(timeoutMs);
297         Assert.assertEquals("number of messages.",
298                 numMessages, getNumReceivedMessages(mTestContext));
299 
300         NativeMidiMessage message = getReceivedMessageAt(mTestContext, 0);
301         compareMessages(buffer, timestamp, message);
302     }
303 
304     @Test
test_D_EchoNMessages()305     public void test_D_EchoNMessages() throws Exception {
306         if (!hasMidiSupport()) {
307             return;
308         }
309         int numMessages = 100;
310         byte[][] buffers = new byte[numMessages][];
311         long timestamps[] = new long[numMessages];
312         generateRandomBufers(buffers, timestamps, numMessages);
313 
314         for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
315             writeMidiWithTimestamp(mTestContext, buffers[msgIndex], 0, buffers[msgIndex].length,
316                     timestamps[msgIndex]);
317         }
318 
319         // Wait for message to pass quickly through echo service.
320         final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
321         Thread.sleep(timeoutMs);
322 
323         // correct number of messages
324         Assert.assertEquals("number of messages.",
325                 numMessages, getNumReceivedMessages(mTestContext));
326 
327         // correct data & order?
328         for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
329             NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
330             compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
331         }
332     }
333 
334     @Test
test_E_FlushMessages()335     public void test_E_FlushMessages() throws Exception {
336         if (!hasMidiSupport()) {
337             return;
338         }
339         int numMessages = 7;
340         byte[][] buffers = new byte[numMessages][];
341         long timestamps[] = new long[numMessages];
342         generateRandomBufers(buffers, timestamps, numMessages);
343 
344         for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
345             writeMidiWithTimestamp(mTestContext, buffers[msgIndex], 0, buffers[msgIndex].length,
346                     timestamps[msgIndex]);
347         }
348 
349         // Wait for message to pass through echo service.
350         final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
351         Thread.sleep(timeoutMs);
352 
353         int result = flushSentMessages(mTestContext);
354         Assert.assertEquals("flush messages failed", 0, result);
355 
356         // correct number of messages
357         Assert.assertEquals("number of messages.",
358                 numMessages, getNumReceivedMessages(mTestContext));
359 
360         // correct data & order?
361         for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
362             NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
363             compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
364         }
365     }
366 
367     @Test
test_F_HugeMessage()368     public void test_F_HugeMessage() throws Exception {
369         if (!hasMidiSupport()) {
370             return;
371         }
372         // Arbitrarily large message.
373         int hugeMessageLen = 1024 * 10;
374         byte[] buffer = generateRandomMessage(hugeMessageLen);
375         int result = writeMidi(mTestContext, buffer, 0, buffer.length);
376         Assert.assertEquals("Huge write failed.", hugeMessageLen, result);
377 
378         int kindaHugeMessageLen = 1024 * 2;
379         buffer = generateRandomMessage(kindaHugeMessageLen);
380         result = writeMidi(mTestContext, buffer, 0, buffer.length);
381         Assert.assertEquals("Kinda big write failed.", kindaHugeMessageLen, result);
382     }
383 
384     /**
385      * Check a large timeout for the echoed messages to come through. If they exceed this
386      * or don't come through at all, something is wrong.
387      */
388     @Test
test_G_NativeEchoTime()389     public void test_G_NativeEchoTime() throws Exception {
390         if (!hasMidiSupport()) {
391             return;
392         }
393         final int numMessages = 10;
394         final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
395         byte[] buffer = {(byte) 0x93, 0, 64};
396 
397         // Send multiple messages in a burst.
398         for (int index = 0; index < numMessages; index++) {
399             buffer[1] = (byte) (60 + index);
400             writeMidiWithTimestamp(mTestContext, buffer, 0, buffer.length, System.nanoTime());
401         }
402 
403         // Wait for messages to pass quickly through echo service.
404         final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
405         Thread.sleep(timeoutMs);
406         Assert.assertEquals("number of messages.",
407                 numMessages, getNumReceivedMessages(mTestContext));
408 
409         for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
410             NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
411             Assert.assertEquals("message index", (byte) (60 + msgIndex), message.buffer[1]);
412             long elapsedNanos = message.timeReceived - message.timestamp;
413             // If this test fails then there may be a problem with the thread scheduler
414             // or there may be kernel activity that is blocking execution at the user level.
415             Assert.assertTrue("MIDI round trip latency index:" + msgIndex
416                             + " too large, " + elapsedNanos
417                             + " nanoseconds " +
418                             "timestamp:" + message.timestamp +
419                             " received:" + message.timeReceived,
420                     (elapsedNanos < maxLatencyNanos));
421         }
422     }
423 
424     @Test
test_H_EchoNMessages_PureNative()425     public void test_H_EchoNMessages_PureNative() throws Exception {
426         if (!hasMidiSupport()) {
427             return;
428         }
429         int numMessages = 2;
430         byte[][] buffers = new byte[numMessages][];
431         long timestamps[] = new long[numMessages];
432         generateRandomBufers(buffers, timestamps, numMessages);
433 
434         for (int msgIndex = 0; msgIndex < numMessages; msgIndex++) {
435             writeMidiWithTimestamp(mTestContext, buffers[msgIndex], 0, buffers[msgIndex].length,
436                     timestamps[msgIndex]);
437         }
438 
439         // Wait for message to pass quickly through echo service.
440         final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
441         Thread.sleep(timeoutMs);
442 
443         int result = matchNativeMessages(mTestContext);
444         Assert.assertEquals("Native Compare Test Failed", result, 0);
445     }
446 
447     /**
448      * Check a large timeout for the echoed messages to come through. If they exceed this
449      * or don't come through at all, something is wrong.
450      */
451     @Test
test_I_NativeEchoTime_PureNative()452     public void test_I_NativeEchoTime_PureNative() throws Exception {
453         if (!hasMidiSupport()) {
454             return;
455         }
456         final int numMessages = 10;
457         final long maxLatencyNanos = 15 * NANOS_PER_MSEC; // generally < 3 msec on N6
458         byte[] buffer = {(byte) 0x93, 0, 64};
459 
460         // Send multiple messages in a burst.
461         for (int index = 0; index < numMessages; index++) {
462             buffer[1] = (byte) (60 + index);
463             writeMidiWithTimestamp(mTestContext, buffer, 0, buffer.length, System.nanoTime());
464         }
465 
466         // Wait for messages to pass through echo service.
467         final int timeoutMs = TIMEOUT_PER_MESSAGE_MS * numMessages;
468         Thread.sleep(timeoutMs);
469         Assert.assertEquals("number of messages.",
470                 numMessages, getNumReceivedMessages(mTestContext));
471 
472         int result = checkNativeLatency(mTestContext, maxLatencyNanos);
473         Assert.assertEquals("failed pure native latency test.", 0, result);
474     }
475 
476     /**
477      * Checks that getDefaultProtocol returns a valid value.
478      */
479     @Test
test_J_GetDefaultProtocol()480     public void test_J_GetDefaultProtocol() throws Exception {
481         if (!hasMidiSupport()) {
482             return;
483         }
484 
485         int defaultProtocol = getDefaultProtocol(mTestContext);
486         Assert.assertEquals("default protocol incorrect.",
487                 MidiDeviceInfo.PROTOCOL_UNKNOWN, defaultProtocol);
488     }
489 
490     // Native Routines
initN()491     public static native void initN();
492 
allocTestContext()493     public static native long allocTestContext();
freeTestContext(long context)494     public static native void freeTestContext(long context);
495 
openNativeMidiDevice(long ctx, MidiDevice device)496     public native int openNativeMidiDevice(long ctx, MidiDevice device);
closeNativeMidiDevice(long ctx)497     public native int closeNativeMidiDevice(long ctx);
498 
startReadingMidi(long ctx, int portNumber)499     public native int startReadingMidi(long ctx, int portNumber);
stopReadingMidi(long ctx)500     public native int stopReadingMidi(long ctx);
501 
startWritingMidi(long ctx, int portNumber)502     public native int startWritingMidi(long ctx, int portNumber);
stopWritingMidi(long ctx)503     public native int stopWritingMidi(long ctx);
504 
writeMidi(long ctx, byte[] data, int offset, int length)505     public native int writeMidi(long ctx, byte[] data, int offset, int length);
writeMidiWithTimestamp(long ctx, byte[] data, int offset, int length, long timestamp)506     public native int writeMidiWithTimestamp(long ctx, byte[] data, int offset, int length,
507             long timestamp);
flushSentMessages(long ctx)508     public native int flushSentMessages(long ctx);
509 
510     // Status - Counters
getNumSends(long ctx)511     public native int getNumSends(long ctx);
getNumBytesSent(long ctx)512     public native int getNumBytesSent(long ctx);
getNumReceives(long ctx)513     public native int getNumReceives(long ctx);
getNumBytesReceived(long ctx)514     public native int getNumBytesReceived(long ctx);
515 
516     // Status - Received Messages
getNumReceivedMessages(long ctx)517     public native int getNumReceivedMessages(long ctx);
getReceivedMessageAt(long ctx, int index)518     public native NativeMidiMessage getReceivedMessageAt(long ctx, int index);
519 
520     // Pure Native Checks
matchNativeMessages(long ctx)521     public native int matchNativeMessages(long ctx);
checkNativeLatency(long ctx, long maxLatencyNanos)522     public native int checkNativeLatency(long ctx, long maxLatencyNanos);
523 
524     // AMidiDevice getters
getDefaultProtocol(long ctx)525     public native int getDefaultProtocol(long ctx);
526 }
527