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.ServiceConnection;
23 import android.content.pm.PackageManager;
24 import android.content.res.Resources;
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.os.Looper;
31 import android.util.Log;
32 import android.view.View;
33 import android.widget.Button;
34 import android.widget.TextView;
35 
36 import com.android.cts.verifier.PassFailButtons;
37 import com.android.cts.verifier.R;
38 import com.android.cts.verifier.audio.midilib.MidiTestModule;
39 import com.android.midi.VerifierMidiEchoService;
40 
41 import java.util.Collection;
42 import java.util.concurrent.Executor;
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 = true;
53 
54     protected MidiManager mMidiManager;
55 
56     protected Intent mMidiServiceIntent;
57     private MidiServiceConnection mMidiServiceConnection;
58 
59     // Flags
60     protected boolean mHasMIDI;
61 
62     private MidiTestModule mUSBTestModule;
63     private MidiTestModule mVirtualTestModule;
64     private MidiTestModule mBTTestModule;
65 
66     // Widgets
67     protected Button mUSBTestBtn;
68     protected Button mVirtTestBtn;
69     protected Button mBTTestBtn;
70 
71     protected TextView    mUSBIInputDeviceLbl;
72     protected TextView    mUSBOutputDeviceLbl;
73     protected TextView    mUSBTestStatusTxt;
74 
75     protected TextView    mVirtInputDeviceLbl;
76     protected TextView    mVirtOutputDeviceLbl;
77     protected TextView    mVirtTestStatusTxt;
78 
79     protected TextView    mBTInputDeviceLbl;
80     protected TextView    mBTOutputDeviceLbl;
81     protected TextView    mBTTestStatusTxt;
82 
MidiTestActivityBase()83     public MidiTestActivityBase() {
84     }
85 
initTestModules(MidiTestModule USBTestModule, MidiTestModule virtualTestModule, MidiTestModule BTTestModule)86     protected void initTestModules(MidiTestModule USBTestModule,
87                                     MidiTestModule virtualTestModule,
88                                     MidiTestModule BTTestModule) {
89         mUSBTestModule = USBTestModule;
90         mVirtualTestModule = virtualTestModule;
91         mBTTestModule = BTTestModule;
92     }
93 
94     @Override
onCreate(Bundle savedInstanceState)95     protected void onCreate(Bundle savedInstanceState) {
96         super.onCreate(savedInstanceState);
97 
98         mMidiManager = getSystemService(MidiManager.class);
99 
100         // Standard PassFailButtons.Activity initialization
101         setPassFailButtonClickListeners();
102         setInfoResources(R.string.midi_test, R.string.midi_info, -1);
103 
104         // May as well calculate this right off the bat.
105         mHasMIDI = hasMIDI();
106         ((TextView)findViewById(R.id.midiHasMIDILbl)).setText("" + mHasMIDI);
107 
108         mUSBTestBtn = (Button)findViewById(R.id.midiTestUSBInterfaceBtn);
109         mUSBTestBtn.setOnClickListener(this);
110         mUSBIInputDeviceLbl = (TextView)findViewById(R.id.midiUSBInputLbl);
111         mUSBOutputDeviceLbl = (TextView)findViewById(R.id.midiUSBOutputLbl);
112         mUSBTestStatusTxt = (TextView)findViewById(R.id.midiUSBTestStatusLbl);
113 
114         mVirtTestBtn = (Button)findViewById(R.id.midiTestVirtInterfaceBtn);
115         mVirtTestBtn.setOnClickListener(this);
116         mVirtInputDeviceLbl = (TextView)findViewById(R.id.midiVirtInputLbl);
117         mVirtOutputDeviceLbl = (TextView)findViewById(R.id.midiVirtOutputLbl);
118         mVirtTestStatusTxt = (TextView)findViewById(R.id.midiVirtTestStatusLbl);
119 
120         mBTTestBtn = (Button)findViewById(R.id.midiTestBTInterfaceBtn);
121         mBTTestBtn.setOnClickListener(this);
122         mBTInputDeviceLbl = (TextView)findViewById(R.id.midiBTInputLbl);
123         mBTOutputDeviceLbl = (TextView)findViewById(R.id.midiBTOutputLbl);
124         mBTTestStatusTxt = (TextView)findViewById(R.id.midiBTTestStatusLbl);
125 
126         calcTestPassed();
127     }
128 
129     @Override
onResume()130     protected void onResume() {
131         super.onResume();
132         if (DEBUG) {
133             Log.i(TAG, "---- Loading Virtual MIDI Service ...");
134         }
135         mMidiServiceConnection = new MidiServiceConnection();
136         boolean isBound =
137                 bindService(mMidiServiceIntent,  mMidiServiceConnection,  Context.BIND_AUTO_CREATE);
138         if (DEBUG) {
139             Log.i(TAG, "---- Virtual MIDI Service loaded: " + isBound);
140         }
141     }
142 
143     @Override
onPause()144     protected void onPause() {
145         super.onPause();
146         if (DEBUG) {
147             Log.i(TAG, "---- onPause()");
148         }
149 
150         unbindService(mMidiServiceConnection);
151         mMidiServiceConnection = null;
152     }
153 
hasMIDI()154     private boolean hasMIDI() {
155         // CDD Section C-1-4: android.software.midi
156         return getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
157     }
158 
startMidiEchoServer()159     void startMidiEchoServer() {
160         // Init MIDI Stuff
161         mMidiServiceIntent = new Intent(this, VerifierMidiEchoService.class);
162     }
163 
connectDeviceListener()164     void connectDeviceListener() {
165         // Plug in device connect/disconnect callback
166         final Handler handler = new Handler(Looper.getMainLooper());
167         final Executor executor = handler::post;
168         mMidiManager.registerDeviceCallback(MidiManager.TRANSPORT_MIDI_BYTE_STREAM,
169                 executor, new MidiDeviceCallback());
170     }
171 
startWiredLoopbackTest()172     void startWiredLoopbackTest() {
173         mUSBTestModule.startLoopbackTest(MidiTestModule.TESTID_USBLOOPBACK);
174     }
175 
startVirtualLoopbackTest()176     void startVirtualLoopbackTest() {
177         mVirtualTestModule.startLoopbackTest(MidiTestModule.TESTID_VIRTUALLOOPBACK);
178     }
179 
startBTLoopbackTest()180     void startBTLoopbackTest() {
181         mBTTestModule.startLoopbackTest(MidiTestModule.TESTID_BTLOOPBACK);
182     }
183 
calcTestPassed()184     boolean calcTestPassed() {
185         boolean hasPassed = false;
186         if (!mHasMIDI) {
187             // if it doesn't report MIDI support, then it doesn't have to pass the other tests.
188             hasPassed = true;
189         } else {
190             hasPassed = mUSBTestModule.hasTestPassed() &&
191                     mVirtualTestModule.hasTestPassed() &&
192                     mBTTestModule.hasTestPassed();
193         }
194 
195         getPassButton().setEnabled(hasPassed);
196         return hasPassed;
197     }
198 
scanMidiDevices()199     void scanMidiDevices() {
200         if (DEBUG) {
201             Log.i(TAG, "scanMidiDevices()....");
202         }
203 
204         // Get the list of all MIDI devices attached
205         Collection<MidiDeviceInfo> devInfos = mMidiManager.getDevicesForTransport(
206                 MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
207         if (DEBUG) {
208             Log.i(TAG, "  numDevices:" + devInfos.size());
209         }
210 
211         // Let each module select (if available) the associated device for their type
212         mUSBTestModule.scanDevices(devInfos);
213         mVirtualTestModule.scanDevices(devInfos);
214         mBTTestModule.scanDevices(devInfos);
215 
216         showConnectedMIDIPeripheral();
217     }
218 
219     //
220     // UI Updaters
221     //
showConnectedMIDIPeripheral()222     void showConnectedMIDIPeripheral() {
223         // USB
224         mUSBIInputDeviceLbl.setText(mUSBTestModule.getInputName());
225         mUSBOutputDeviceLbl.setText(mUSBTestModule.getOutputName());
226         mUSBTestBtn.setEnabled(mUSBTestModule.isTestReady());
227 
228         // Virtual MIDI
229         mVirtInputDeviceLbl.setText(mVirtualTestModule.getInputName());
230         mVirtOutputDeviceLbl.setText(mVirtualTestModule.getOutputName());
231         mVirtTestBtn.setEnabled(mVirtualTestModule.isTestReady());
232 
233         // Bluetooth
234         mBTInputDeviceLbl.setText(mBTTestModule.getInputName());
235         mBTOutputDeviceLbl.setText(mBTTestModule.getOutputName());
236         // use mUSBTestModule.isTestReady() as a proxy for knowing the interface loopback
237         // is connected
238         mBTTestBtn.setEnabled(mBTTestModule.isTestReady() && mUSBTestModule.isTestReady());
239     }
240 
241     //
242     // UI Updaters
243     //
showUSBTestStatus()244     void showUSBTestStatus() {
245         mUSBTestStatusTxt.setText(getTestStatusString(mUSBTestModule.getTestStatus()));
246     }
247 
showVirtTestStatus()248     void showVirtTestStatus() {
249         mVirtTestStatusTxt.setText(getTestStatusString(mVirtualTestModule.getTestStatus()));
250     }
251 
showBTTestStatus()252     void showBTTestStatus() {
253         mBTTestStatusTxt.setText(getTestStatusString(mBTTestModule.getTestStatus()));
254     }
255 
enableTestButtons(boolean enable)256     void enableTestButtons(boolean enable) {
257         runOnUiThread(new Runnable() {
258             public void run() {
259                 if (enable) {
260                     // remember, a given test might not be enabled, so we can't just enable
261                     // all of the buttons
262                     showConnectedMIDIPeripheral();
263                 } else {
264                     mUSBTestBtn.setEnabled(enable);
265                     mVirtTestBtn.setEnabled(enable);
266                     mBTTestBtn.setEnabled(enable);
267                 }
268             }
269         });
270     }
271 
272     // Need this to update UI from MIDI read thread
updateTestStateUI()273     public void updateTestStateUI() {
274         runOnUiThread(new Runnable() {
275             public void run() {
276                 calcTestPassed();
277                 showUSBTestStatus();
278                 showVirtTestStatus();
279                 showBTTestStatus();
280             }
281         });
282     }
283 
284     // UI Helper
getTestStatusString(int status)285     public String getTestStatusString(int status) {
286         Resources appResources = getApplicationContext().getResources();
287         switch (status) {
288             case MidiTestModule.TESTSTATUS_NOTRUN:
289                 return appResources.getString(R.string.midiNotRunLbl);
290 
291             case MidiTestModule.TESTSTATUS_PASSED:
292                 return appResources.getString(R.string.midiPassedLbl);
293 
294             case MidiTestModule.TESTSTATUS_FAILED_MISMATCH:
295                 return appResources.getString(R.string.midiFailedMismatchLbl);
296 
297             case MidiTestModule.TESTSTATUS_FAILED_TIMEOUT:
298                 return appResources.getString(R.string.midiFailedTimeoutLbl);
299 
300             case MidiTestModule.TESTSTATUS_FAILED_OVERRUN:
301                 return appResources.getString(R.string.midiFailedOverrunLbl);
302 
303             case MidiTestModule.TESTSTATUS_FAILED_DEVICE:
304                 return appResources.getString(R.string.midiFailedDeviceLbl);
305 
306             case MidiTestModule.TESTSTATUS_FAILED_JNI:
307                 return appResources.getString(R.string.midiFailedJNILbl);
308 
309             case MidiTestModule.TESTSTATUS_FAILED_SETUP:
310                 return appResources.getString(R.string.midiFailedSetupLbl);
311 
312             case MidiTestModule.TESTSTATUS_FAILED_SEND:
313                 return appResources.getString(R.string.midiFailedSendLbl);
314 
315             default:
316                 return "Unknown Test Status.";
317         }
318     }
319 
320     //
321     // View.OnClickListener Override - Handles button clicks
322     //
323     @Override
onClick(View view)324     public void onClick(View view) {
325         int id = view.getId();
326         if (id == R.id.midiTestUSBInterfaceBtn) {
327             startWiredLoopbackTest();
328         } else if (id == R.id.midiTestVirtInterfaceBtn) {
329             startVirtualLoopbackTest();
330         } else if (id == R.id.midiTestBTInterfaceBtn) {
331             startBTLoopbackTest();
332         } else {
333             assert false : "Unhandled button click";
334         }
335     }
336 
337     class MidiServiceConnection implements ServiceConnection {
338         private static final String TAG = "MidiServiceConnection";
339         @Override
onServiceConnected(ComponentName name, IBinder service)340         public void  onServiceConnected(ComponentName name, IBinder service) {
341             if (DEBUG) {
342                 Log.i(TAG, "MidiServiceConnection.onServiceConnected()");
343             }
344             scanMidiDevices();
345         }
346 
347         @Override
onServiceDisconnected(ComponentName name)348         public void onServiceDisconnected(ComponentName name) {
349             if (DEBUG) {
350                 Log.i(TAG, "MidiServiceConnection.onServiceDisconnected()");
351             }
352         }
353     }
354 
355     /**
356      * Callback class for MIDI device connect/disconnect.
357      */
358     class MidiDeviceCallback extends MidiManager.DeviceCallback {
359         private static final String TAG = "MidiDeviceCallback";
360 
361         @Override
onDeviceAdded(MidiDeviceInfo device)362         public void onDeviceAdded(MidiDeviceInfo device) {
363             scanMidiDevices();
364         }
365 
366         @Override
onDeviceRemoved(MidiDeviceInfo device)367         public void onDeviceRemoved(MidiDeviceInfo device) {
368             scanMidiDevices();
369         }
370     } /* class MidiDeviceCallback */
371 }
372