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