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