1 /*
2  * Copyright (C) 2021 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.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.res.Resources;
24 import android.content.ServiceConnection;
25 import android.media.midi.MidiDeviceInfo;
26 import android.media.midi.MidiManager;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.util.Log;
31 import android.view.View;
32 import android.widget.Button;
33 import android.widget.TextView;
34 
35 import com.android.cts.verifier.audio.midilib.MidiIODevice;
36 import com.android.cts.verifier.PassFailButtons;
37 import com.android.cts.verifier.R;  // needed to access resource in CTSVerifier project namespace.
38 
39 import com.android.midi.VerifierMidiEchoService;
40 
41 import java.util.Timer;
42 import java.util.TimerTask;
43 
44 /**
45  * Common information and behaviors for the MidiJavaTestActivity and MidiNativeTestActivity
46  */
47 public abstract class MidiTestActivityBase
48         extends PassFailButtons.Activity
49         implements View.OnClickListener {
50 
51     private static final String TAG = "MidiTestActivityBase";
52     private static final boolean DEBUG = false;
53 
54     protected MidiManager mMidiManager;
55 
56     protected Intent mMidiServiceIntent;
57     private MidiServiceConnection mMidiServiceConnection;
58 
59     // Flags
60     protected boolean mHasMIDI;
61 
62     // Test Status
63     protected static final int TESTSTATUS_NOTRUN = 0;
64     protected static final int TESTSTATUS_PASSED = 1;
65     protected static final int TESTSTATUS_FAILED_MISMATCH = 2;
66     protected static final int TESTSTATUS_FAILED_TIMEOUT = 3;
67     protected static final int TESTSTATUS_FAILED_OVERRUN = 4;
68     protected static final int TESTSTATUS_FAILED_DEVICE = 5;
69     protected static final int TESTSTATUS_FAILED_JNI = 6;
70 
71     private MidiTestModule mUSBTestModule;
72     private MidiTestModule mVirtualTestModule;
73     private MidiTestModule mBTTestModule;
74 
75     // Widgets
76     protected Button mUSBTestBtn;
77     protected Button mVirtTestBtn;
78     protected Button mBTTestBtn;
79 
80     protected TextView    mUSBIInputDeviceLbl;
81     protected TextView    mUSBOutputDeviceLbl;
82     protected TextView    mUSBTestStatusTxt;
83 
84     protected TextView    mVirtInputDeviceLbl;
85     protected TextView    mVirtOutputDeviceLbl;
86     protected TextView    mVirtTestStatusTxt;
87 
88     protected TextView    mBTInputDeviceLbl;
89     protected TextView    mBTOutputDeviceLbl;
90     protected TextView    mBTTestStatusTxt;
91 
92     protected static final int TESTID_NONE = 0;
93     protected static final int TESTID_USBLOOPBACK = 1;
94     protected static final int TESTID_VIRTUALLOOPBACK = 2;
95     protected static final int TESTID_BTLOOPBACK = 3;
96     protected int mRunningTestID = TESTID_NONE;
97 
MidiTestActivityBase()98     public MidiTestActivityBase() {
99     }
100 
initTestModules(MidiTestModule USBTestModule, MidiTestModule virtualTestModule, MidiTestModule BTTestModule)101     protected void initTestModules(MidiTestModule USBTestModule,
102                                     MidiTestModule virtualTestModule,
103                                     MidiTestModule BTTestModule) {
104         mUSBTestModule = USBTestModule;
105         mVirtualTestModule = virtualTestModule;
106         mBTTestModule = BTTestModule;
107     }
108 
109     @Override
onCreate(Bundle savedInstanceState)110     protected void onCreate(Bundle savedInstanceState) {
111         super.onCreate(savedInstanceState);
112 
113         mMidiManager = getSystemService(MidiManager.class);
114 
115         // Standard PassFailButtons.Activity initialization
116         setPassFailButtonClickListeners();
117         setInfoResources(R.string.midi_test, R.string.midi_info, -1);
118 
119         // May as well calculate this right off the bat.
120         mHasMIDI = hasMIDI();
121         ((TextView)findViewById(R.id.midiHasMIDILbl)).setText("" + mHasMIDI);
122 
123         mUSBTestBtn = (Button)findViewById(R.id.midiTestUSBInterfaceBtn);
124         mUSBTestBtn.setOnClickListener(this);
125         mUSBIInputDeviceLbl = (TextView)findViewById(R.id.midiUSBInputLbl);
126         mUSBOutputDeviceLbl = (TextView)findViewById(R.id.midiUSBOutputLbl);
127         mUSBTestStatusTxt = (TextView)findViewById(R.id.midiUSBTestStatusLbl);
128 
129         mVirtTestBtn = (Button)findViewById(R.id.midiTestVirtInterfaceBtn);
130         mVirtTestBtn.setOnClickListener(this);
131         mVirtInputDeviceLbl = (TextView)findViewById(R.id.midiVirtInputLbl);
132         mVirtOutputDeviceLbl = (TextView)findViewById(R.id.midiVirtOutputLbl);
133         mVirtTestStatusTxt = (TextView)findViewById(R.id.midiVirtTestStatusLbl);
134 
135         mBTTestBtn = (Button)findViewById(R.id.midiTestBTInterfaceBtn);
136         mBTTestBtn.setOnClickListener(this);
137         mBTInputDeviceLbl = (TextView)findViewById(R.id.midiBTInputLbl);
138         mBTOutputDeviceLbl = (TextView)findViewById(R.id.midiBTOutputLbl);
139         mBTTestStatusTxt = (TextView)findViewById(R.id.midiBTTestStatusLbl);
140 
141         calcTestPassed();
142     }
143 
144     @Override
onResume()145     protected void onResume() {
146         super.onResume();
147         if (DEBUG) {
148             Log.i(TAG, "---- Loading Virtual MIDI Service ...");
149         }
150         mMidiServiceConnection = new MidiServiceConnection();
151         boolean isBound =
152                 bindService(mMidiServiceIntent,  mMidiServiceConnection,  Context.BIND_AUTO_CREATE);
153         if (DEBUG) {
154             Log.i(TAG, "---- Virtual MIDI Service loaded: " + isBound);
155         }
156     }
157 
158     @Override
onPause()159     protected void onPause() {
160         super.onPause();
161         if (DEBUG) {
162             Log.i(TAG, "---- onPause()");
163         }
164 
165         unbindService(mMidiServiceConnection);
166         mMidiServiceConnection = null;
167     }
168 
hasMIDI()169     private boolean hasMIDI() {
170         // CDD Section C-1-4: android.software.midi
171         return getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
172     }
173 
startMidiEchoServer()174     void startMidiEchoServer() {
175         // Init MIDI Stuff
176         mMidiServiceIntent = new Intent(this, VerifierMidiEchoService.class);
177     }
178 
connectDeviceListener()179     void connectDeviceListener() {
180         // Plug in device connect/disconnect callback
181         mMidiManager.registerDeviceCallback(new MidiDeviceCallback(), new Handler(getMainLooper()));
182     }
183 
startWiredLoopbackTest()184     void startWiredLoopbackTest() {
185         mUSBTestModule.startLoopbackTest(TESTID_USBLOOPBACK);
186     }
187 
startVirtualLoopbackTest()188     void startVirtualLoopbackTest() {
189         mVirtualTestModule.startLoopbackTest(TESTID_VIRTUALLOOPBACK);
190     }
191 
startBTLoopbackTest()192     void startBTLoopbackTest() {
193         mBTTestModule.startLoopbackTest(TESTID_BTLOOPBACK);
194     }
195 
calcTestPassed()196     boolean calcTestPassed() {
197         boolean hasPassed = false;
198         if (!mHasMIDI) {
199             // if it doesn't report MIDI support, then it doesn't have to pass the other tests.
200             hasPassed = true;
201         } else {
202             hasPassed = mUSBTestModule.hasTestPassed() &&
203                     mVirtualTestModule.hasTestPassed() &&
204                     mBTTestModule.hasTestPassed();
205         }
206 
207         getPassButton().setEnabled(hasPassed);
208         return hasPassed;
209     }
210 
scanMidiDevices()211     void scanMidiDevices() {
212         if (DEBUG) {
213             Log.i(TAG, "scanMidiDevices()....");
214         }
215 
216         MidiDeviceInfo[] devInfos = mMidiManager.getDevices();
217         mUSBTestModule.scanDevices(devInfos);
218         mVirtualTestModule.scanDevices(devInfos);
219         mBTTestModule.scanDevices(devInfos);
220 
221         showConnectedMIDIPeripheral();
222     }
223 
224     //
225     // UI Updaters
226     //
showConnectedMIDIPeripheral()227     void showConnectedMIDIPeripheral() {
228         // USB
229         mUSBIInputDeviceLbl.setText(mUSBTestModule.getInputName());
230         mUSBOutputDeviceLbl.setText(mUSBTestModule.getOutputName());
231         mUSBTestBtn.setEnabled(mUSBTestModule.isTestReady());
232 
233         // Virtual MIDI
234         mVirtInputDeviceLbl.setText(mVirtualTestModule.getInputName());
235         mVirtOutputDeviceLbl.setText(mVirtualTestModule.getOutputName());
236         mVirtTestBtn.setEnabled(mVirtualTestModule.isTestReady());
237 
238         // Bluetooth
239         mBTInputDeviceLbl.setText(mBTTestModule.getInputName());
240         mBTOutputDeviceLbl.setText(mBTTestModule.getOutputName());
241         // use mUSBTestModule.isTestReady() as a proxy for knowing the interface loopback
242         // is connected
243         mBTTestBtn.setEnabled(mBTTestModule.isTestReady() && mUSBTestModule.isTestReady());
244     }
245 
246     //
247     // UI Updaters
248     //
showUSBTestStatus()249     void showUSBTestStatus() {
250         mUSBTestStatusTxt.setText(getTestStatusString(mUSBTestModule.getTestStatus()));
251     }
252 
showVirtTestStatus()253     void showVirtTestStatus() {
254         mVirtTestStatusTxt.setText(getTestStatusString(mVirtualTestModule.getTestStatus()));
255     }
256 
showBTTestStatus()257     void showBTTestStatus() {
258         mBTTestStatusTxt.setText(getTestStatusString(mBTTestModule.getTestStatus()));
259     }
260 
enableTestButtons(boolean enable)261     void enableTestButtons(boolean enable) {
262         runOnUiThread(new Runnable() {
263             public void run() {
264                 if (enable) {
265                     // remember, a given test might not be enabled, so we can't just enable
266                     // all of the buttons
267                     showConnectedMIDIPeripheral();
268                 } else {
269                     mUSBTestBtn.setEnabled(enable);
270                     mVirtTestBtn.setEnabled(enable);
271                     mBTTestBtn.setEnabled(enable);
272                 }
273             }
274         });
275     }
276 
277     // Need this to update UI from MIDI read thread
updateTestStateUI()278     public void updateTestStateUI() {
279         runOnUiThread(new Runnable() {
280             public void run() {
281                 calcTestPassed();
282                 showUSBTestStatus();
283                 showVirtTestStatus();
284                 showBTTestStatus();
285             }
286         });
287     }
288 
289     // UI Helper
getTestStatusString(int status)290     public String getTestStatusString(int status) {
291         Resources appResources = getApplicationContext().getResources();
292         switch (status) {
293             case TESTSTATUS_NOTRUN:
294                 return appResources.getString(R.string.midiNotRunLbl);
295 
296             case TESTSTATUS_PASSED:
297                 return appResources.getString(R.string.midiPassedLbl);
298 
299             case TESTSTATUS_FAILED_MISMATCH:
300                 return appResources.getString(R.string.midiFailedMismatchLbl);
301 
302             case TESTSTATUS_FAILED_TIMEOUT:
303                 return appResources.getString(R.string.midiFailedTimeoutLbl);
304 
305             case TESTSTATUS_FAILED_OVERRUN:
306                 return appResources.getString(R.string.midiFailedOverrunLbl);
307 
308             case TESTSTATUS_FAILED_DEVICE:
309                 return appResources.getString(R.string.midiFailedDeviceLbl);
310 
311             case TESTSTATUS_FAILED_JNI:
312                 return appResources.getString(R.string.midiFailedJNILbl);
313 
314             default:
315                 return "Unknown Test Status.";
316         }
317     }
318 
319     //
320     // View.OnClickListener Override - Handles button clicks
321     //
322     @Override
onClick(View view)323     public void onClick(View view) {
324         switch (view.getId()) {
325         case R.id.midiTestUSBInterfaceBtn:
326             startWiredLoopbackTest();
327             break;
328 
329         case R.id.midiTestVirtInterfaceBtn:
330             startVirtualLoopbackTest();
331             break;
332 
333         case R.id.midiTestBTInterfaceBtn:
334             startBTLoopbackTest();
335             break;
336 
337         default:
338             assert false : "Unhandled button click";
339         }
340     }
341 
342     class MidiServiceConnection implements ServiceConnection {
343         private static final String TAG = "MidiServiceConnection";
344         @Override
onServiceConnected(ComponentName name, IBinder service)345         public void  onServiceConnected(ComponentName name, IBinder service) {
346             if (DEBUG) {
347                 Log.i(TAG, "MidiServiceConnection.onServiceConnected()");
348             }
349             scanMidiDevices();
350         }
351 
352         @Override
onServiceDisconnected(ComponentName name)353         public void onServiceDisconnected(ComponentName name) {
354             if (DEBUG) {
355                 Log.i(TAG, "MidiServiceConnection.onServiceDisconnected()");
356             }
357         }
358     }
359 
360     /**
361      * Callback class for MIDI device connect/disconnect.
362      */
363     class MidiDeviceCallback extends MidiManager.DeviceCallback {
364         private static final String TAG = "MidiDeviceCallback";
365 
366         @Override
onDeviceAdded(MidiDeviceInfo device)367         public void onDeviceAdded(MidiDeviceInfo device) {
368             scanMidiDevices();
369         }
370 
371         @Override
onDeviceRemoved(MidiDeviceInfo device)372         public void onDeviceRemoved(MidiDeviceInfo device) {
373             scanMidiDevices();
374         }
375     } /* class MidiDeviceCallback */
376 
377     abstract class MidiTestModule {
378         protected int mTestStatus = TESTSTATUS_NOTRUN;
379 
380         // The Test Peripheral
381         MidiIODevice                mIODevice;
382 
383         // Test State
384         protected final Object        mTestLock = new Object();
385         protected boolean             mTestRunning;
386 
387         // Timeout handling
388         protected static final int    TEST_TIMEOUT_MS = 1000;
389         protected final Timer         mTimeoutTimer = new Timer();
390 
MidiTestModule(int deviceType)391         public MidiTestModule(int deviceType) {
392             mIODevice = new MidiIODevice(deviceType);
393         }
394 
startLoopbackTest(int testID)395         abstract void startLoopbackTest(int testID);
hasTestPassed()396         abstract boolean hasTestPassed();
397 
getTestStatus()398         public int getTestStatus() { return mTestStatus; }
399 
isTestReady()400         public boolean isTestReady() {
401             return mIODevice.mReceiveDevInfo != null && mIODevice.mSendDevInfo != null;
402         }
403 
getInputName()404         public String getInputName() {
405             return mIODevice.getInputName();
406         }
407 
getOutputName()408         public String getOutputName() {
409             return mIODevice.getOutputName();
410         }
411 
scanDevices(MidiDeviceInfo[] devInfos)412         public void scanDevices(MidiDeviceInfo[] devInfos) {
413             mIODevice.scanDevices(devInfos);
414         }
415 
showTimeoutMessage()416         void showTimeoutMessage() {
417             runOnUiThread(new Runnable() {
418                 public void run() {
419                     synchronized (mTestLock) {
420                         if (mTestRunning) {
421                             if (DEBUG) {
422                                 Log.i(TAG, "---- Test Failed - TIMEOUT");
423                             }
424                             mTestStatus = TESTSTATUS_FAILED_TIMEOUT;
425                             updateTestStateUI();
426                         }
427                     }
428                 }
429             });
430         }
431 
startTimeoutHandler()432         void startTimeoutHandler() {
433             // Start the timeout timer
434             TimerTask task = new TimerTask() {
435                 @Override
436                 public void run() {
437                     synchronized (mTestLock) {
438                         if (mTestRunning) {
439                             // Timeout
440                             showTimeoutMessage();
441                             enableTestButtons(true);
442                         }
443                     }
444                 }
445             };
446             mTimeoutTimer.schedule(task, TEST_TIMEOUT_MS);
447         }
448     }
449 
450 }
451