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 com.android.cts.verifier.audio; 18 19 import android.media.midi.MidiDevice; 20 import android.media.midi.MidiDeviceInfo; 21 import android.media.midi.MidiManager; 22 import android.media.midi.MidiReceiver; 23 import android.os.Bundle; 24 import android.util.Log; 25 26 import com.android.compatibility.common.util.CddTest; 27 import com.android.cts.verifier.R; 28 import com.android.cts.verifier.audio.midilib.MidiIODevice; 29 import com.android.cts.verifier.audio.midilib.MidiTestModule; 30 import com.android.cts.verifier.audio.midilib.NativeMidiManager; 31 32 import java.io.IOException; 33 import java.util.Collection; 34 35 /* 36 * A note about the USB MIDI device. 37 * Any USB MIDI peripheral with standard female DIN jacks can be used. A standard MIDI cable 38 * plugged into both input and output is required for the USB Loopback Test. A Bluetooth MIDI 39 * device like the Yamaha MD-BT01 plugged into both input and output is required for the 40 * Bluetooth Loopback test. 41 */ 42 43 /* 44 * A note about the "virtual MIDI" device... 45 * See file MidiEchoService for implementation of the echo server itself. 46 * This service is started by the main manifest file (AndroidManifest.xml). 47 */ 48 49 /* 50 * A note about Bluetooth MIDI devices... 51 * Any Bluetooth MIDI device needs to be paired with the DUT with the "MIDI+BTLE" application 52 * available in the Play Store: 53 * (https://play.google.com/store/apps/details?id=com.mobileer.example.midibtlepairing). 54 */ 55 56 /** 57 * CTS Verifier Activity for MIDI test 58 */ 59 @CddTest(requirement = "5.9/C-1-3,C-1-2") 60 public class MidiNativeTestActivity extends MidiTestActivityBase { 61 private static final String TAG = "MidiNativeTestActivity"; 62 private static final boolean DEBUG = false; 63 MidiNativeTestActivity()64 public MidiNativeTestActivity() { 65 super(); 66 initTestModules(new NativeMidiTestModule(MidiDeviceInfo.TYPE_USB), 67 new NativeMidiTestModule(MidiDeviceInfo.TYPE_VIRTUAL), 68 new BTMidiTestModule()); 69 70 NativeMidiManager.loadNativeAPI(); 71 NativeMidiManager.initN(); 72 } 73 74 @Override onCreate(Bundle savedInstanceState)75 protected void onCreate(Bundle savedInstanceState) { 76 if (DEBUG) { 77 Log.i(TAG, "---- onCreate()"); 78 } 79 80 setContentView(R.layout.ndk_midi_activity); 81 82 super.onCreate(savedInstanceState); 83 84 startMidiEchoServer(); 85 scanMidiDevices(); 86 87 connectDeviceListener(); 88 } 89 90 @Override onPause()91 protected void onPause () { 92 super.onPause(); 93 if (DEBUG) { 94 Log.i(TAG, "---- onPause()"); 95 } 96 97 boolean isFound = stopService(mMidiServiceIntent); 98 if (DEBUG) { 99 Log.i(TAG, "---- Stop Service: " + isFound); 100 } 101 } 102 103 /** 104 * A class to control and represent the state of a given test. 105 * It hold the data needed for IO, and the logic for sending, receiving and matching 106 * the MIDI data stream. 107 */ 108 public class NativeMidiTestModule extends MidiTestModule { 109 private static final String TAG = "NativeMidiTestModule"; 110 private static final boolean DEBUG = true; 111 112 private NativeMidiManager mNativeMidiManager; 113 NativeMidiTestModule(int deviceType)114 public NativeMidiTestModule(int deviceType) { 115 super(deviceType); 116 mNativeMidiManager = new NativeMidiManager(); 117 118 // this call is just to keep the build from stripping out "endTest", because 119 // it is only called from JNI. 120 endTest(TESTSTATUS_NOTRUN); 121 } 122 123 @Override updateTestStateUIAbstract()124 protected void updateTestStateUIAbstract() { 125 updateTestStateUI(); 126 } 127 128 @Override showTimeoutMessageAbstract()129 protected void showTimeoutMessageAbstract() { 130 showTimeoutMessage(); 131 } 132 133 @Override enableTestButtonsAbstract(boolean enable)134 protected void enableTestButtonsAbstract(boolean enable) { 135 enableTestButtons(enable); 136 } 137 showTimeoutMessage()138 void showTimeoutMessage() { 139 runOnUiThread(new Runnable() { 140 public void run() { 141 synchronized (mTestLock) { 142 if (mTestRunning) { 143 if (DEBUG) { 144 Log.i(TAG, "---- Test Failed - TIMEOUT"); 145 } 146 mTestStatus = TESTSTATUS_FAILED_TIMEOUT; 147 updateTestStateUIAbstract(); 148 } 149 } 150 } 151 }); 152 } 153 closePorts()154 protected void closePorts() { 155 // NOP 156 } 157 158 @Override startLoopbackTest(int testID)159 public void startLoopbackTest(int testID) { 160 synchronized (mTestLock) { 161 mTestCounter++; 162 mTestRunning = true; 163 enableTestButtons(false); 164 } 165 166 if (DEBUG) { 167 Log.i(TAG, "---- startLoopbackTest()"); 168 } 169 170 synchronized (mTestLock) { 171 mTestStatus = TESTSTATUS_NOTRUN; 172 } 173 174 if (mIODevice.mSendDevInfo != null) { 175 mMidiManager.openDevice(mIODevice.mSendDevInfo, new TestModuleOpenListener(), null); 176 } 177 178 startTimeoutHandler(); 179 } 180 181 @Override hasTestPassed()182 public boolean hasTestPassed() { 183 int status; 184 synchronized (mTestLock) { 185 status = mTestStatus; 186 } 187 return status == TESTSTATUS_PASSED; 188 } 189 endTest(int endCode)190 public void endTest(int endCode) { 191 synchronized (mTestLock) { 192 mTestRunning = false; 193 mTestStatus = endCode; 194 } 195 if (endCode != TESTSTATUS_NOTRUN) { 196 updateTestStateUI(); 197 enableTestButtons(true); 198 } 199 200 closePorts(); 201 } 202 203 /** 204 * Listens for MIDI device opens. Opens I/O ports and sends out the apriori 205 * setup messages. 206 */ 207 class TestModuleOpenListener implements MidiManager.OnDeviceOpenedListener { 208 // 209 // This is where the logical part of the test starts 210 // 211 @Override onDeviceOpened(MidiDevice device)212 public void onDeviceOpened(MidiDevice device) { 213 if (DEBUG) { 214 Log.i(TAG, "---- onDeviceOpened()"); 215 } 216 mNativeMidiManager.startTest(NativeMidiTestModule.this, device, 217 false /*throttleData*/); 218 } 219 } 220 } /* class NativeMidiTestModule */ 221 222 /** 223 * Test Module for Bluetooth Loopback. 224 * This is a specialization of NativeMidiTestModule (which has the connections for the BL device 225 * itself) with and added MidiIODevice object for the USB audio device which does the 226 * "looping back". 227 */ 228 private class BTMidiTestModule extends NativeMidiTestModule { 229 private static final String TAG = "BTMidiTestModule"; 230 private MidiIODevice mUSBLoopbackDevice = new MidiIODevice(MidiDeviceInfo.TYPE_USB); 231 BTMidiTestModule()232 public BTMidiTestModule() { 233 super(MidiDeviceInfo.TYPE_BLUETOOTH); 234 } 235 236 @Override scanDevices(Collection<MidiDeviceInfo> devInfos)237 public void scanDevices(Collection<MidiDeviceInfo> devInfos) { 238 // (normal) Scan for BT MIDI device 239 super.scanDevices(devInfos); 240 // Find a USB Loopback Device 241 mUSBLoopbackDevice.scanDevices(devInfos); 242 } 243 closePorts()244 protected void closePorts() { 245 super.closePorts(); 246 if (mUSBLoopbackDevice != null) { 247 mUSBLoopbackDevice.closePorts(); 248 } 249 } 250 251 @Override startLoopbackTest(int testID)252 public void startLoopbackTest(int testID) { 253 if (DEBUG) { 254 Log.i(TAG, "---- startLoopbackTest()"); 255 } 256 // Setup the USB Loopback Device 257 mUSBLoopbackDevice.closePorts(); 258 259 if (mIODevice.mSendDevInfo != null) { 260 mMidiManager.openDevice( 261 mUSBLoopbackDevice.mSendDevInfo, new USBLoopbackOpenListener(), null); 262 } 263 264 // Now start the test as usual 265 super.startLoopbackTest(testID); 266 } 267 268 /** 269 * We need this OnDeviceOpenedListener to open the USB-Loopback device 270 */ 271 private class USBLoopbackOpenListener implements MidiManager.OnDeviceOpenedListener { 272 @Override onDeviceOpened(MidiDevice device)273 public void onDeviceOpened(MidiDevice device) { 274 if (DEBUG) { 275 Log.i("USBLoopbackOpenListener", "---- onDeviceOpened()"); 276 } 277 mUSBLoopbackDevice.openPorts(device, new USBMidiEchoReceiver()); 278 } 279 } /* class USBLoopbackOpenListener */ 280 281 /** 282 * MidiReceiver subclass for BlueTooth Loopback Test 283 * 284 * This class receives bytes from the USB Interface (presumably coming from the 285 * Bluetooth MIDI peripheral) and echoes them back out (presumably to the Bluetooth 286 * MIDI peripheral). 287 */ 288 //TODO - This could be pulled out into a separate class and shared with the identical 289 // code in MidiJavaTestActivity is we pass in the send port 290 private class USBMidiEchoReceiver extends MidiReceiver { 291 private int mTotalBytesEchoed; 292 293 @Override onSend(byte[] msg, int offset, int count, long timestamp)294 public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { 295 mTotalBytesEchoed += count; 296 if (DEBUG) { 297 Log.i(TAG, "---- USBMidiEchoReceiver.onSend() count:" + count + 298 " total:" + mTotalBytesEchoed); 299 } 300 if (mUSBLoopbackDevice.mSendPort == null) { 301 Log.e(TAG, "(native) mUSBLoopbackDevice.mSendPort is null"); 302 } else { 303 mUSBLoopbackDevice.mSendPort.onSend(msg, offset, count, timestamp); 304 } 305 } 306 } /* class USBMidiEchoReceiver */ 307 } /* class BTMidiTestModule */ 308 } /* class MidiNativeTestActivity */ 309