1 /* 2 * Copyright 2019 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.google.sample.oboe.manualtest; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.Bundle; 24 import android.view.View; 25 import android.widget.Button; 26 import android.widget.TextView; 27 28 import java.io.IOException; 29 30 /** 31 * Guide the user through a series of tests plugging in and unplugging a headset. 32 * Print a summary at the end of any failures. 33 */ 34 public class TestDisconnectActivity extends TestAudioActivity { 35 36 private static final String TEXT_SKIP = "SKIP"; 37 private static final String TEXT_PASS = "PASS"; 38 private static final String TEXT_FAIL = "FAIL !!!!"; 39 public static final int POLL_DURATION_MILLIS = 50; 40 public static final int SETTLING_TIME_MILLIS = 600; 41 public static final int TIME_TO_FAILURE_MILLIS = 3000; 42 43 private TextView mInstructionsTextView; 44 private TextView mStatusTextView; 45 private TextView mPlugTextView; 46 47 private volatile boolean mTestFailed; 48 private volatile boolean mSkipTest; 49 private volatile int mPlugCount; 50 private BroadcastReceiver mPluginReceiver = new PluginBroadcastReceiver(); 51 private Button mFailButton; 52 private Button mSkipButton; 53 54 protected AutomatedTestRunner mAutomatedTestRunner; 55 56 // Receive a broadcast Intent when a headset is plugged in or unplugged. 57 // Display a count on screen. 58 public class PluginBroadcastReceiver extends BroadcastReceiver { 59 @Override onReceive(Context context, Intent intent)60 public void onReceive(Context context, Intent intent) { 61 mPlugCount++; 62 runOnUiThread(new Runnable() { 63 @Override 64 public void run() { 65 String message = "Intent.HEADSET_PLUG #" + mPlugCount; 66 mPlugTextView.setText(message); 67 } 68 }); 69 } 70 } 71 72 @Override inflateActivity()73 protected void inflateActivity() { 74 setContentView(R.layout.activity_test_disconnect); 75 } 76 77 @Override onCreate(Bundle savedInstanceState)78 protected void onCreate(Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 81 mAutomatedTestRunner = findViewById(R.id.auto_test_runner); 82 mAutomatedTestRunner.setActivity(this); 83 84 mInstructionsTextView = (TextView) findViewById(R.id.text_instructions); 85 mStatusTextView = (TextView) findViewById(R.id.text_status); 86 mPlugTextView = (TextView) findViewById(R.id.text_plug_events); 87 88 mFailButton = (Button) findViewById(R.id.button_fail); 89 mSkipButton = (Button) findViewById(R.id.button_skip); 90 updateFailSkipButton(false); 91 } 92 93 @Override getTestName()94 public String getTestName() { 95 return "Disconnect"; 96 } 97 getActivityType()98 int getActivityType() { 99 return ACTIVITY_TEST_DISCONNECT; 100 } 101 102 @Override isOutput()103 boolean isOutput() { 104 return true; 105 } 106 107 @Override setupEffects(int sessionId)108 public void setupEffects(int sessionId) { 109 } 110 updateFailSkipButton(final boolean running)111 private void updateFailSkipButton(final boolean running) { 112 runOnUiThread(new Runnable() { 113 @Override 114 public void run() { 115 mFailButton.setEnabled(running); 116 mSkipButton.setEnabled(running); 117 } 118 }); 119 } 120 121 // Write to status and command view setInstructionsText(final String text)122 private void setInstructionsText(final String text) { 123 runOnUiThread(new Runnable() { 124 @Override 125 public void run() { 126 mInstructionsTextView.setText(text); 127 } 128 }); 129 } 130 131 // Write to status and command view setStatusText(final String text)132 private void setStatusText(final String text) { 133 runOnUiThread(new Runnable() { 134 @Override 135 public void run() { 136 mStatusTextView.setText(text); 137 } 138 }); 139 } 140 141 @Override onResume()142 public void onResume() { 143 super.onResume(); 144 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); 145 this.registerReceiver(mPluginReceiver, filter); 146 } 147 148 @Override onPause()149 public void onPause() { 150 this.unregisterReceiver(mPluginReceiver); 151 super.onPause(); 152 } 153 154 // This should only be called from UI events such as onStop or a button press. 155 @Override onStopTest()156 public void onStopTest() { 157 mAutomatedTestRunner.stopTest(); 158 } 159 startAudioTest()160 public void startAudioTest() throws IOException { 161 startAudio(); 162 } 163 stopAudioTest()164 public void stopAudioTest() { 165 stopAudioQuiet(); 166 closeAudio(); 167 } 168 onCancel(View view)169 public void onCancel(View view) { 170 stopAudioTest(); 171 mAutomatedTestRunner.onTestFinished(); 172 } 173 174 // Called on UI thread onStopAudioTest(View view)175 public void onStopAudioTest(View view) { 176 stopAudioTest(); 177 mAutomatedTestRunner.onTestFinished(); 178 keepScreenOn(false); 179 } 180 onFailTest(View view)181 public void onFailTest(View view) { 182 mTestFailed = true; 183 } 184 onSkipTest(View view)185 public void onSkipTest(View view) { 186 mSkipTest = true; 187 } 188 getConfigText(StreamConfiguration config)189 private String getConfigText(StreamConfiguration config) { 190 return ((config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) ? "OUT" : "IN") 191 + ", Perf = " + StreamConfiguration.convertPerformanceModeToText( 192 config.getPerformanceMode()) 193 + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode()) 194 + ", " + config.getSampleRate(); 195 } 196 log(String text)197 private void log(String text) { 198 mAutomatedTestRunner.log(text); 199 } 200 appendFailedSummary(String text)201 private void appendFailedSummary(String text) { 202 mAutomatedTestRunner.appendFailedSummary(text); 203 } 204 testConfiguration(boolean isInput, int perfMode, int sharingMode, int sampleRate, boolean requestPlugin)205 private void testConfiguration(boolean isInput, 206 int perfMode, 207 int sharingMode, 208 int sampleRate, 209 boolean requestPlugin) throws InterruptedException { 210 String actualConfigText = "none"; 211 mSkipTest = false; 212 213 AudioInputTester mAudioInTester = null; 214 AudioOutputTester mAudioOutTester = null; 215 216 clearStreamContexts(); 217 218 if (isInput) { 219 mAudioInTester = addAudioInputTester(); 220 } else { 221 mAudioOutTester = addAudioOutputTester(); 222 } 223 224 // Configure settings 225 StreamConfiguration requestedConfig = (isInput) 226 ? mAudioInTester.requestedConfiguration 227 : mAudioOutTester.requestedConfiguration; 228 StreamConfiguration actualConfig = (isInput) 229 ? mAudioInTester.actualConfiguration 230 : mAudioOutTester.actualConfiguration; 231 232 requestedConfig.reset(); 233 requestedConfig.setPerformanceMode(perfMode); 234 requestedConfig.setSharingMode(sharingMode); 235 requestedConfig.setSampleRate(sampleRate); 236 if (sampleRate != 0) { 237 requestedConfig.setRateConversionQuality(StreamConfiguration.RATE_CONVERSION_QUALITY_MEDIUM); 238 } 239 240 log("========================== #" + mAutomatedTestRunner.getTestCount()); 241 log("Requested:"); 242 log(getConfigText(requestedConfig)); 243 244 // Give previous stream time to close and release resources. Avoid race conditions. 245 Thread.sleep(SETTLING_TIME_MILLIS); 246 if (!mAutomatedTestRunner.isThreadEnabled()) return; 247 boolean openFailed = false; 248 AudioStreamBase stream = null; 249 try { 250 openAudio(); 251 log("Actual:"); 252 actualConfigText = getConfigText(actualConfig) 253 + ", " + (actualConfig.isMMap() ? "MMAP" : "Legacy"); 254 log(actualConfigText); 255 256 stream = (isInput) 257 ? mAudioInTester.getCurrentAudioStream() 258 : mAudioOutTester.getCurrentAudioStream(); 259 } catch (IOException e) { 260 openFailed = true; 261 log(e.getMessage()); 262 } 263 264 // The test is only worth running if we got the configuration we requested. 265 boolean valid = true; 266 if (!openFailed) { 267 if(actualConfig.getSharingMode() != sharingMode) { 268 log("did not get requested sharing mode"); 269 valid = false; 270 } 271 if (actualConfig.getPerformanceMode() != perfMode) { 272 log("did not get requested performance mode"); 273 valid = false; 274 } 275 if (actualConfig.getNativeApi() == StreamConfiguration.NATIVE_API_OPENSLES) { 276 log("OpenSL ES does not support automatic disconnect"); 277 valid = false; 278 } 279 } 280 281 if (!openFailed && valid) { 282 try { 283 startAudioTest(); 284 } catch (IOException e) { 285 e.printStackTrace(); 286 valid = false; 287 log(e.getMessage()); 288 } 289 } 290 291 int oldPlugCount = mPlugCount; 292 if (!openFailed && valid) { 293 mTestFailed = false; 294 updateFailSkipButton(true); 295 // poll until stream started 296 while (!mTestFailed && mAutomatedTestRunner.isThreadEnabled() && !mSkipTest && 297 stream.getState() == StreamConfiguration.STREAM_STATE_STARTING) { 298 Thread.sleep(POLL_DURATION_MILLIS); 299 } 300 String message = (requestPlugin ? "Plug IN" : "UNplug") + " headset now!"; 301 setStatusText("Testing:\n" + actualConfigText); 302 setInstructionsText(message); 303 int timeoutCount = 0; 304 // Wait for Java plug count to change or stream to disconnect. 305 while (!mTestFailed && mAutomatedTestRunner.isThreadEnabled() && !mSkipTest && 306 stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) { 307 Thread.sleep(POLL_DURATION_MILLIS); 308 if (mPlugCount > oldPlugCount) { 309 timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS; 310 break; 311 } 312 } 313 // Wait for timeout or stream to disconnect. 314 while (!mTestFailed && mAutomatedTestRunner.isThreadEnabled() && !mSkipTest && (timeoutCount > 0) && 315 stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) { 316 Thread.sleep(POLL_DURATION_MILLIS); 317 timeoutCount--; 318 if (timeoutCount == 0) { 319 mTestFailed = true; 320 } else { 321 setStatusText("Plug detected by Java.\nCounting down to Oboe failure: " + timeoutCount); 322 } 323 } 324 if (!mTestFailed) { 325 int error = stream.getLastErrorCallbackResult(); 326 if (error != StreamConfiguration.ERROR_DISCONNECTED) { 327 log("onEerrorCallback error = " + error 328 + ", expected " + StreamConfiguration.ERROR_DISCONNECTED); 329 mTestFailed = true; 330 } 331 } 332 setStatusText(mTestFailed ? "Failed" : "Passed - detected"); 333 } 334 updateFailSkipButton(false); 335 setInstructionsText("Wait..."); 336 337 if (!openFailed) { 338 stopAudioTest(); 339 } 340 341 if (mSkipTest) valid = false; 342 343 if (valid) { 344 if (openFailed) { 345 appendFailedSummary("------ #" + mAutomatedTestRunner.getTestCount() + "\n"); 346 appendFailedSummary(getConfigText(requestedConfig) + "\n"); 347 appendFailedSummary("Open failed!\n"); 348 mAutomatedTestRunner.incrementFailCount(); 349 } else { 350 log("Result:"); 351 boolean passed = !mTestFailed; 352 String resultText = requestPlugin ? "plugIN" : "UNplug"; 353 resultText += ", " + (passed ? TEXT_PASS : TEXT_FAIL); 354 log(resultText); 355 if (!passed) { 356 appendFailedSummary("------ #" + mAutomatedTestRunner.getTestCount() + "\n"); 357 appendFailedSummary(" " + actualConfigText + "\n"); 358 appendFailedSummary(" " + resultText + "\n"); 359 mAutomatedTestRunner.incrementFailCount(); 360 } else { 361 mAutomatedTestRunner.incrementPassCount(); 362 } 363 } 364 } else { 365 log(TEXT_SKIP); 366 } 367 // Give hardware time to settle between tests. 368 Thread.sleep(1000); 369 mAutomatedTestRunner.incrementTestCount(); 370 } 371 testConfiguration(boolean isInput, int performanceMode, int sharingMode, int sampleRate)372 private void testConfiguration(boolean isInput, int performanceMode, 373 int sharingMode, int sampleRate) throws InterruptedException { 374 boolean requestPlugin = true; // plug IN 375 testConfiguration(isInput, performanceMode, sharingMode, sampleRate, requestPlugin); 376 requestPlugin = false; // UNplug 377 testConfiguration(isInput, performanceMode, sharingMode, sampleRate, requestPlugin); 378 } 379 testConfiguration(boolean isInput, int performanceMode, int sharingMode)380 private void testConfiguration(boolean isInput, int performanceMode, 381 int sharingMode) throws InterruptedException { 382 final int sampleRate = 0; 383 testConfiguration(isInput, performanceMode, sharingMode, sampleRate); 384 } 385 testConfiguration(int performanceMode, int sharingMode)386 private void testConfiguration(int performanceMode, 387 int sharingMode) throws InterruptedException { 388 testConfiguration(false, performanceMode, sharingMode); 389 testConfiguration(true, performanceMode, sharingMode); 390 } 391 392 @Override runTest()393 public void runTest() { 394 mPlugCount = 0; 395 // Try several different configurations. 396 try { 397 testConfiguration(false, StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, 398 StreamConfiguration.SHARING_MODE_EXCLUSIVE, 44100); 399 testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, 400 StreamConfiguration.SHARING_MODE_EXCLUSIVE); 401 testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, 402 StreamConfiguration.SHARING_MODE_SHARED); 403 testConfiguration(StreamConfiguration.PERFORMANCE_MODE_NONE, 404 StreamConfiguration.SHARING_MODE_SHARED); 405 } catch (InterruptedException e) { 406 log(e.getMessage()); 407 showErrorToast(e.getMessage()); 408 } finally { 409 setInstructionsText("Test completed."); 410 updateFailSkipButton(false); 411 } 412 } 413 } 414