1 /* 2 * Copyright (C) 2015 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.midi.cts; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.media.midi.MidiManager; 22 import android.media.midi.MidiOutputPort; 23 import android.media.midi.MidiDevice; 24 import android.media.midi.MidiDevice.MidiConnection; 25 import android.media.midi.MidiDeviceInfo; 26 import android.media.midi.MidiDeviceInfo.PortInfo; 27 import android.media.midi.MidiDeviceStatus; 28 import android.media.midi.MidiInputPort; 29 import android.media.midi.MidiReceiver; 30 import android.media.midi.MidiSender; 31 import android.os.Bundle; 32 import android.util.Log; 33 import android.test.AndroidTestCase; 34 35 import com.android.midi.CTSMidiEchoTestService; 36 import com.android.midi.MidiEchoTestService; 37 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.Random; 41 42 /** 43 * Test MIDI using a virtual MIDI device that echos input to output. 44 */ 45 public class MidiEchoTest extends AndroidTestCase { 46 private static final String TAG = "MidiEchoTest"; 47 private static final boolean DEBUG = false; 48 49 // I am overloading the timestamp for some tests. It is passed 50 // directly through the Echo server unchanged. 51 // The high 32-bits has a recognizable value. 52 // The low 32-bits can contain data used to identify messages. 53 private static final long TIMESTAMP_MARKER = 0x1234567800000000L; 54 private static final long TIMESTAMP_MARKER_MASK = 0xFFFFFFFF00000000L; 55 private static final long TIMESTAMP_DATA_MASK = 0x00000000FFFFFFFFL; 56 private static final long NANOS_PER_MSEC = 1000L * 1000L; 57 58 // On a fast device in 2016, the test fails if timeout is 3 but works if it is 4. 59 // So this timeout value is very generous. 60 private static final int TIMEOUT_OPEN_MSEC = 1000; // arbitrary 61 // On a fast device in 2016, the test fails if timeout is 0 but works if it is 1. 62 // So this timeout value is very generous. 63 private static final int TIMEOUT_STATUS_MSEC = 500; // arbitrary 64 65 // This is defined in MidiPortImpl.java as the maximum payload that 66 // can be sent internally by MidiInputPort in a 67 // SOCK_SEQPACKET datagram. 68 private static final int MAX_PACKET_DATA_SIZE = 1024 - 9; 69 70 // Store device and ports related to the Echo service. 71 static class MidiTestContext { 72 MidiDeviceInfo echoInfo; 73 MidiDevice echoDevice; 74 MidiInputPort echoInputPort; 75 MidiOutputPort echoOutputPort; 76 } 77 78 // Store complete MIDI message so it can be put in an array. 79 static class MidiMessage { 80 public final byte[] data; 81 public final long timestamp; 82 public final long timeReceived; 83 MidiMessage(byte[] buffer, int offset, int length, long timestamp)84 MidiMessage(byte[] buffer, int offset, int length, long timestamp) { 85 timeReceived = System.nanoTime(); 86 data = new byte[length]; 87 System.arraycopy(buffer, offset, data, 0, length); 88 this.timestamp = timestamp; 89 } 90 } 91 92 // Listens for an asynchronous device open and notifies waiting foreground 93 // test. 94 class MyTestOpenCallback implements MidiManager.OnDeviceOpenedListener { 95 MidiDevice mDevice; 96 97 @Override onDeviceOpened(MidiDevice device)98 public synchronized void onDeviceOpened(MidiDevice device) { 99 mDevice = device; 100 notifyAll(); 101 } 102 waitForOpen(int msec)103 public synchronized MidiDevice waitForOpen(int msec) 104 throws InterruptedException { 105 long deadline = System.currentTimeMillis() + msec; 106 long timeRemaining = msec; 107 while (mDevice == null && timeRemaining > 0) { 108 wait(timeRemaining); 109 timeRemaining = deadline - System.currentTimeMillis(); 110 } 111 return mDevice; 112 } 113 } 114 115 // Store received messages in an array. 116 class MyLoggingReceiver extends MidiReceiver { 117 ArrayList<MidiMessage> messages = new ArrayList<MidiMessage>(); 118 int mByteCount; 119 120 @Override onSend(byte[] data, int offset, int count, long timestamp)121 public synchronized void onSend(byte[] data, int offset, int count, 122 long timestamp) { 123 messages.add(new MidiMessage(data, offset, count, timestamp)); 124 mByteCount += count; 125 notifyAll(); 126 } 127 getMessageCount()128 public synchronized int getMessageCount() { 129 return messages.size(); 130 } 131 getByteCount()132 public synchronized int getByteCount() { 133 return mByteCount; 134 } 135 getMessage(int index)136 public synchronized MidiMessage getMessage(int index) { 137 return messages.get(index); 138 } 139 140 /** 141 * Wait until count messages have arrived. This is a cumulative total. 142 * 143 * @param count 144 * @param timeoutMs 145 * @throws InterruptedException 146 */ waitForMessages(int count, int timeoutMs)147 public synchronized void waitForMessages(int count, int timeoutMs) 148 throws InterruptedException { 149 long endTimeMs = System.currentTimeMillis() + timeoutMs + 1; 150 long timeToWait = timeoutMs + 1; 151 while ((getMessageCount() < count) 152 && (timeToWait > 0)) { 153 wait(timeToWait); 154 timeToWait = endTimeMs - System.currentTimeMillis(); 155 } 156 } 157 158 /** 159 * Wait until count bytes have arrived. This is a cumulative total. 160 * 161 * @param count 162 * @param timeoutMs 163 * @throws InterruptedException 164 */ waitForBytes(int count, int timeoutMs)165 public synchronized void waitForBytes(int count, int timeoutMs) 166 throws InterruptedException { 167 long endTimeMs = System.currentTimeMillis() + timeoutMs + 1; 168 long timeToWait = timeoutMs + 1; 169 while ((getByteCount() < count) 170 && (timeToWait > 0)) { 171 wait(timeToWait); 172 timeToWait = endTimeMs - System.currentTimeMillis(); 173 } 174 } 175 } 176 177 @Override setUp()178 protected void setUp() throws Exception { 179 super.setUp(); 180 } 181 182 @Override tearDown()183 protected void tearDown() throws Exception { 184 super.tearDown(); 185 } 186 setUpEchoServer()187 protected MidiTestContext setUpEchoServer() throws Exception { 188 if (DEBUG) { 189 Log.i(TAG, "setUpEchoServer()"); 190 } 191 MidiManager midiManager = (MidiManager) mContext.getSystemService( 192 Context.MIDI_SERVICE); 193 194 MidiDeviceInfo echoInfo = CTSMidiEchoTestService.findEchoDevice(mContext); 195 196 // Open device. 197 MyTestOpenCallback callback = new MyTestOpenCallback(); 198 midiManager.openDevice(echoInfo, callback, null); 199 MidiDevice echoDevice = callback.waitForOpen(TIMEOUT_OPEN_MSEC); 200 assertTrue("could not open " 201 + CTSMidiEchoTestService.getEchoServerName(), echoDevice != null); 202 203 // Query echo service directly to see if it is getting status updates. 204 MidiEchoTestService echoService = CTSMidiEchoTestService.getInstance(); 205 assertEquals("virtual device status, input port before open", false, 206 echoService.inputOpened); 207 assertEquals("virtual device status, output port before open", 0, 208 echoService.outputOpenCount); 209 210 // Open input port. 211 MidiInputPort echoInputPort = echoDevice.openInputPort(0); 212 assertTrue("could not open input port", echoInputPort != null); 213 assertEquals("input port number", 0, echoInputPort.getPortNumber()); 214 assertEquals("virtual device status, input port after open", true, 215 echoService.inputOpened); 216 assertEquals("virtual device status, output port before open", 0, 217 echoService.outputOpenCount); 218 219 // Open output port. 220 MidiOutputPort echoOutputPort = echoDevice.openOutputPort(0); 221 assertTrue("could not open output port", echoOutputPort != null); 222 assertEquals("output port number", 0, echoOutputPort.getPortNumber()); 223 assertEquals("virtual device status, input port after open", true, 224 echoService.inputOpened); 225 assertEquals("virtual device status, output port after open", 1, 226 echoService.outputOpenCount); 227 228 MidiTestContext mc = new MidiTestContext(); 229 mc.echoInfo = echoInfo; 230 mc.echoDevice = echoDevice; 231 mc.echoInputPort = echoInputPort; 232 mc.echoOutputPort = echoOutputPort; 233 return mc; 234 } 235 236 /** 237 * Close ports and check device status. 238 * 239 * @param mc 240 */ tearDownEchoServer(MidiTestContext mc)241 protected void tearDownEchoServer(MidiTestContext mc) throws IOException { 242 // Query echo service directly to see if it is getting status updates. 243 MidiEchoTestService echoService = CTSMidiEchoTestService.getInstance(); 244 assertEquals("virtual device status, input port before close", true, 245 echoService.inputOpened); 246 assertEquals("virtual device status, output port before close", 1, 247 echoService.outputOpenCount); 248 249 // Close output port. 250 mc.echoOutputPort.close(); 251 assertEquals("virtual device status, input port before close", true, 252 echoService.inputOpened); 253 assertEquals("virtual device status, output port after close", 0, 254 echoService.outputOpenCount); 255 mc.echoOutputPort.close(); 256 mc.echoOutputPort.close(); // should be safe to close twice 257 258 // Close input port. 259 mc.echoInputPort.close(); 260 assertEquals("virtual device status, input port after close", false, 261 echoService.inputOpened); 262 assertEquals("virtual device status, output port after close", 0, 263 echoService.outputOpenCount); 264 mc.echoInputPort.close(); 265 mc.echoInputPort.close(); // should be safe to close twice 266 267 mc.echoDevice.close(); 268 mc.echoDevice.close(); // should be safe to close twice 269 } 270 271 /** 272 * @param mc 273 * @param echoInfo 274 */ checkEchoDeviceInfo(MidiTestContext mc, MidiDeviceInfo echoInfo)275 protected void checkEchoDeviceInfo(MidiTestContext mc, 276 MidiDeviceInfo echoInfo) { 277 assertEquals("echo input port count wrong", 1, 278 echoInfo.getInputPortCount()); 279 assertEquals("echo output port count wrong", 1, 280 echoInfo.getOutputPortCount()); 281 282 Bundle properties = echoInfo.getProperties(); 283 String tags = (String) properties.get("tags"); 284 assertEquals("attributes from device XML", "echo,test", tags); 285 286 PortInfo[] ports = echoInfo.getPorts(); 287 assertEquals("port info array size", 2, ports.length); 288 289 boolean foundInput = false; 290 boolean foundOutput = false; 291 for (PortInfo portInfo : ports) { 292 if (portInfo.getType() == PortInfo.TYPE_INPUT) { 293 foundInput = true; 294 assertEquals("input port name", "input", portInfo.getName()); 295 296 assertEquals("info port number", portInfo.getPortNumber(), 297 mc.echoInputPort.getPortNumber()); 298 } else if (portInfo.getType() == PortInfo.TYPE_OUTPUT) { 299 foundOutput = true; 300 assertEquals("output port name", "output", portInfo.getName()); 301 assertEquals("info port number", portInfo.getPortNumber(), 302 mc.echoOutputPort.getPortNumber()); 303 } 304 } 305 assertTrue("found input port info", foundInput); 306 assertTrue("found output port info", foundOutput); 307 308 assertEquals("MIDI device type", MidiDeviceInfo.TYPE_VIRTUAL, 309 echoInfo.getType()); 310 } 311 312 // Is the MidiManager supported? testMidiManager()313 public void testMidiManager() throws Exception { 314 PackageManager pm = mContext.getPackageManager(); 315 if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) { 316 return; // Not supported so don't test it. 317 } 318 319 MidiManager midiManager = (MidiManager) mContext.getSystemService( 320 Context.MIDI_SERVICE); 321 assertTrue("MidiManager not supported.", midiManager != null); 322 323 // There should be at least one device for the Echo server. 324 MidiDeviceInfo[] infos = midiManager.getDevices(); 325 assertTrue("device list was null", infos != null); 326 assertTrue("device list was empty", infos.length >= 1); 327 } 328 testDeviceInfo()329 public void testDeviceInfo() throws Exception { 330 PackageManager pm = mContext.getPackageManager(); 331 if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) { 332 return; // Not supported so don't test it. 333 } 334 335 MidiTestContext mc = setUpEchoServer(); 336 checkEchoDeviceInfo(mc, mc.echoInfo); 337 checkEchoDeviceInfo(mc, mc.echoDevice.getInfo()); 338 assertTrue("device info equal", 339 mc.echoInfo.equals(mc.echoDevice.getInfo())); 340 tearDownEchoServer(mc); 341 } 342 testEchoSmallMessage()343 public void testEchoSmallMessage() throws Exception { 344 checkEchoVariableMessage(3); 345 } 346 testEchoLargeMessage()347 public void testEchoLargeMessage() throws Exception { 348 checkEchoVariableMessage(MAX_PACKET_DATA_SIZE); 349 } 350 351 // This message will not fit in the internal buffer in MidiInputPort. 352 // But it is still a legal size according to the API for 353 // MidiReceiver.send(). It may be received in multiple packets. testEchoOversizeMessage()354 public void testEchoOversizeMessage() throws Exception { 355 checkEchoVariableMessage(MAX_PACKET_DATA_SIZE + 20); 356 } 357 358 // Send a variable sized message. The actual 359 // size will be a multiple of 3 because it sends NoteOns. checkEchoVariableMessage(int messageSize)360 public void checkEchoVariableMessage(int messageSize) throws Exception { 361 PackageManager pm = mContext.getPackageManager(); 362 if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) { 363 return; // Not supported so don't test it. 364 } 365 366 MidiTestContext mc = setUpEchoServer(); 367 368 MyLoggingReceiver receiver = new MyLoggingReceiver(); 369 mc.echoOutputPort.connect(receiver); 370 371 // Send an integral number of notes 372 int numNotes = messageSize / 3; 373 int noteSize = numNotes * 3; 374 final byte[] buffer = new byte[noteSize]; 375 int index = 0; 376 for (int i = 0; i < numNotes; i++) { 377 buffer[index++] = (byte) (0x90 + (i & 0x0F)); // NoteOn 378 buffer[index++] = (byte) 0x47; // Pitch 379 buffer[index++] = (byte) 0x52; // Velocity 380 }; 381 long timestamp = 0x0123765489ABFEDCL; 382 383 mc.echoInputPort.send(buffer, 0, 0, timestamp); // should be a NOOP 384 mc.echoInputPort.send(buffer, 0, buffer.length, timestamp); 385 mc.echoInputPort.send(buffer, 0, 0, timestamp); // should be a NOOP 386 387 // Wait for message to pass quickly through echo service. 388 // Message sent may have been split into multiple received messages. 389 // So wait until we receive all the expected bytes. 390 final int numBytesExpected = buffer.length; 391 final int timeoutMs = 20; 392 synchronized (receiver) { 393 receiver.waitForBytes(numBytesExpected, timeoutMs); 394 } 395 396 // Check total size. 397 final int numReceived = receiver.getMessageCount(); 398 int totalBytesReceived = 0; 399 for (int i = 0; i < numReceived; i++) { 400 MidiMessage message = receiver.getMessage(i); 401 totalBytesReceived += message.data.length; 402 assertEquals("timestamp in message", timestamp, message.timestamp); 403 } 404 assertEquals("byte count of messages", numBytesExpected, 405 totalBytesReceived); 406 407 // Make sure the payload was not corrupted. 408 int sentIndex = 0; 409 for (int i = 0; i < numReceived; i++) { 410 MidiMessage message = receiver.getMessage(i); 411 for (int k = 0; k < message.data.length; k++) { 412 assertEquals("message byte[" + i + "]", 413 buffer[sentIndex++] & 0x0FF, 414 message.data[k] & 0x0FF); 415 } 416 } 417 418 mc.echoOutputPort.disconnect(receiver); 419 tearDownEchoServer(mc); 420 } 421 testEchoLatency()422 public void testEchoLatency() throws Exception { 423 PackageManager pm = mContext.getPackageManager(); 424 if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) { 425 return; // Not supported so don't test it. 426 } 427 428 MidiTestContext mc = setUpEchoServer(); 429 MyLoggingReceiver receiver = new MyLoggingReceiver(); 430 mc.echoOutputPort.connect(receiver); 431 432 final int numMessages = 10; 433 final int maxLatencyMs = 15; // generally < 3 msec on N6 434 final long maxLatencyNanos = maxLatencyMs * NANOS_PER_MSEC; 435 byte[] buffer = { 436 (byte) 0x93, 0, 64 437 }; 438 439 // Send multiple messages in a burst. 440 for (int index = 0; index < numMessages; index++) { 441 buffer[1] = (byte) (60 + index); 442 mc.echoInputPort.send(buffer, 0, buffer.length, System.nanoTime()); 443 } 444 445 // Wait for messages to pass quickly through echo service. 446 final int timeoutMs = (numMessages * maxLatencyMs) + 20; 447 synchronized (receiver) { 448 receiver.waitForMessages(numMessages, timeoutMs); 449 } 450 assertEquals("number of messages.", numMessages, receiver.getMessageCount()); 451 452 for (int index = 0; index < numMessages; index++) { 453 MidiMessage message = receiver.getMessage(index); 454 assertEquals("message index", (byte) (60 + index), message.data[1]); 455 long elapsedNanos = message.timeReceived - message.timestamp; 456 // If this test fails then there may be a problem with the thread scheduler 457 // or there may be kernel activity that is blocking execution at the user level. 458 assertTrue("MIDI round trip latency[" + index + "] too large, " + elapsedNanos 459 + " nanoseconds", 460 (elapsedNanos < maxLatencyNanos)); 461 } 462 463 mc.echoOutputPort.disconnect(receiver); 464 tearDownEchoServer(mc); 465 } 466 testEchoMultipleMessages()467 public void testEchoMultipleMessages() throws Exception { 468 PackageManager pm = mContext.getPackageManager(); 469 if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) { 470 return; // Not supported so don't test it. 471 } 472 473 MidiTestContext mc = setUpEchoServer(); 474 475 MyLoggingReceiver receiver = new MyLoggingReceiver(); 476 mc.echoOutputPort.connect(receiver); 477 478 final byte[] buffer = new byte[2048]; 479 480 final int numMessages = 100; 481 Random random = new Random(1972941337); 482 int bytesSent = 0; 483 byte value = 0; 484 485 // Send various length messages with sequential bytes. 486 long timestamp = TIMESTAMP_MARKER; 487 for (int messageIndex = 0; messageIndex < numMessages; messageIndex++) { 488 // Sweep numData across critical region of 489 // MidiPortImpl.MAX_PACKET_DATA_SIZE 490 int numData = 1000 + messageIndex; 491 for (int dataIndex = 0; dataIndex < numData; dataIndex++) { 492 buffer[dataIndex] = value; 493 value++; 494 } 495 // This may get split into multiple sends internally. 496 mc.echoInputPort.send(buffer, 0, numData, timestamp); 497 bytesSent += numData; 498 timestamp++; 499 } 500 501 // Check messages. Data must be sequential bytes. 502 value = 0; 503 int bytesReceived = 0; 504 int messageReceivedIndex = 0; 505 int messageSentIndex = 0; 506 int expectedMessageSentIndex = 0; 507 while (bytesReceived < bytesSent) { 508 final int timeoutMs = 500; 509 // Wait for next message. 510 synchronized (receiver) { 511 receiver.waitForMessages(messageReceivedIndex + 1, timeoutMs); 512 } 513 MidiMessage message = receiver.getMessage(messageReceivedIndex++); 514 // parse timestamp marker and data 515 long timestampMarker = message.timestamp & TIMESTAMP_MARKER_MASK; 516 assertEquals("timestamp marker corrupted", TIMESTAMP_MARKER, timestampMarker); 517 messageSentIndex = (int) (message.timestamp & TIMESTAMP_DATA_MASK); 518 519 int numData = message.data.length; 520 for (int dataIndex = 0; dataIndex < numData; dataIndex++) { 521 String msg = String.format("message[%d/%d].data[%d/%d]", 522 messageReceivedIndex, messageSentIndex, dataIndex, 523 numData); 524 assertEquals(msg, value, message.data[dataIndex]); 525 value++; 526 } 527 bytesReceived += numData; 528 // May not advance if message got split 529 if (messageSentIndex > expectedMessageSentIndex) { 530 expectedMessageSentIndex++; // only advance by one each message 531 } 532 assertEquals("timestamp in message", expectedMessageSentIndex, 533 messageSentIndex); 534 } 535 536 mc.echoOutputPort.disconnect(receiver); 537 tearDownEchoServer(mc); 538 } 539 540 // What happens if the app does bad things. testEchoBadBehavior()541 public void testEchoBadBehavior() throws Exception { 542 PackageManager pm = mContext.getPackageManager(); 543 if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) { 544 return; // Not supported so don't test it. 545 } 546 MidiTestContext mc = setUpEchoServer(); 547 548 // This should fail because it is already open. 549 MidiInputPort echoInputPort2 = mc.echoDevice.openInputPort(0); 550 assertTrue("input port opened twice", echoInputPort2 == null); 551 552 tearDownEchoServer(mc); 553 } 554 555 // Store history of status changes. 556 private class MyDeviceCallback extends MidiManager.DeviceCallback { 557 private MidiDeviceStatus mStatus; 558 private MidiDeviceInfo mInfo; 559 MyDeviceCallback(MidiDeviceInfo info)560 public MyDeviceCallback(MidiDeviceInfo info) { 561 mInfo = info; 562 } 563 564 @Override onDeviceStatusChanged(MidiDeviceStatus status)565 public synchronized void onDeviceStatusChanged(MidiDeviceStatus status) { 566 super.onDeviceStatusChanged(status); 567 // Filter out status reports from unrelated devices. 568 if (mInfo.equals(status.getDeviceInfo())) { 569 mStatus = status; 570 notifyAll(); 571 } 572 } 573 574 // Wait for a timeout or a notify(). 575 // Return status message or a null if it times out. waitForStatus(int msec)576 public synchronized MidiDeviceStatus waitForStatus(int msec) 577 throws InterruptedException { 578 long deadline = System.currentTimeMillis() + msec; 579 long timeRemaining = msec; 580 while (mStatus == null && timeRemaining > 0) { 581 wait(timeRemaining); 582 timeRemaining = deadline - System.currentTimeMillis(); 583 } 584 return mStatus; 585 } 586 587 clear()588 public synchronized void clear() { 589 mStatus = null; 590 } 591 } 592 593 // Test callback for onDeviceStatusChanged(). testDeviceCallback()594 public void testDeviceCallback() throws Exception { 595 596 PackageManager pm = mContext.getPackageManager(); 597 if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) { 598 return; // Not supported so don't test it. 599 } 600 MidiManager midiManager = (MidiManager) mContext.getSystemService( 601 Context.MIDI_SERVICE); 602 603 MidiDeviceInfo echoInfo = CTSMidiEchoTestService.findEchoDevice(mContext); 604 605 // Open device. 606 MyTestOpenCallback callback = new MyTestOpenCallback(); 607 midiManager.openDevice(echoInfo, callback, null); 608 MidiDevice echoDevice = callback.waitForOpen(TIMEOUT_OPEN_MSEC); 609 assertTrue("could not open " + CTSMidiEchoTestService.getEchoServerName(), echoDevice != null); 610 MyDeviceCallback deviceCallback = new MyDeviceCallback(echoInfo); 611 try { 612 613 midiManager.registerDeviceCallback(deviceCallback, null); 614 615 MidiDeviceStatus status = deviceCallback.waitForStatus(TIMEOUT_STATUS_MSEC); 616 assertEquals("we should not have any status yet", null, status); 617 618 // Open input port. 619 MidiInputPort echoInputPort = echoDevice.openInputPort(0); 620 assertTrue("could not open input port", echoInputPort != null); 621 622 status = deviceCallback.waitForStatus(TIMEOUT_STATUS_MSEC); 623 assertTrue("should have status by now", null != status); 624 assertEquals("input port open?", true, status.isInputPortOpen(0)); 625 626 deviceCallback.clear(); 627 echoInputPort.close(); 628 status = deviceCallback.waitForStatus(TIMEOUT_STATUS_MSEC); 629 assertTrue("should have status by now", null != status); 630 assertEquals("input port closed?", false, status.isInputPortOpen(0)); 631 632 // Make sure we do NOT get called after unregistering. 633 midiManager.unregisterDeviceCallback(deviceCallback); 634 deviceCallback.clear(); 635 echoInputPort = echoDevice.openInputPort(0); 636 assertTrue("could not open input port", echoInputPort != null); 637 638 status = deviceCallback.waitForStatus(TIMEOUT_STATUS_MSEC); 639 assertEquals("should not get status after unregistering", null, status); 640 641 echoInputPort.close(); 642 } finally { 643 // Safe to call twice. 644 midiManager.unregisterDeviceCallback(deviceCallback); 645 echoDevice.close(); 646 } 647 } 648 } 649