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