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