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.Build; 24 import android.os.Bundle; 25 import android.text.method.ScrollingMovementMethod; 26 import android.view.View; 27 import android.widget.Button; 28 import android.widget.TextView; 29 30 import java.io.IOException; 31 import java.util.Date; 32 33 /** 34 * Guide the user through a series of tests plugging in and unplugging a headset. 35 * Print a summary at the end of any failures. 36 * 37 * TODO Test Input 38 */ 39 public class TestDisconnectActivity extends TestAudioActivity implements Runnable { 40 41 private static final String TEXT_SKIP = "SKIP"; 42 private static final String TEXT_PASS = "PASS"; 43 private static final String TEXT_FAIL = "FAIL !!!!"; 44 public static final int POLL_DURATION_MILLIS = 50; 45 public static final int SETTLING_TIME_MILLIS = 600; 46 public static final int TIME_TO_FAILURE_MILLIS = 3000; 47 48 private TextView mInstructionsTextView; 49 private TextView mAutoTextView; 50 private TextView mStatusTextView; 51 private TextView mPlugTextView; 52 53 private Thread mAutoThread; 54 private volatile boolean mThreadEnabled; 55 private volatile boolean mTestFailed; 56 private volatile boolean mSkipTest; 57 private volatile int mPlugCount; 58 private int mTestCount; 59 private StringBuffer mFailedSummary; 60 private int mPassCount; 61 private int mFailCount; 62 private BroadcastReceiver mPluginReceiver = new PluginBroadcastReceiver(); 63 private Button mStartButton; 64 private Button mStopButton; 65 private Button mShareButton; 66 private Button mFailButton; 67 private Button mSkipButton; 68 69 // Receive a broadcast Intent when a headset is plugged in or unplugged. 70 // Display a count on screen. 71 public class PluginBroadcastReceiver extends BroadcastReceiver { 72 @Override onReceive(Context context, Intent intent)73 public void onReceive(Context context, Intent intent) { 74 mPlugCount++; 75 runOnUiThread(new Runnable() { 76 @Override 77 public void run() { 78 String message = "Intent.HEADSET_PLUG #" + mPlugCount; 79 mPlugTextView.setText(message); 80 } 81 }); 82 } 83 } 84 85 @Override inflateActivity()86 protected void inflateActivity() { 87 setContentView(R.layout.activity_test_disconnect); 88 } 89 90 @Override onCreate(Bundle savedInstanceState)91 protected void onCreate(Bundle savedInstanceState) { 92 super.onCreate(savedInstanceState); 93 94 mInstructionsTextView = (TextView) findViewById(R.id.text_instructions); 95 mStatusTextView = (TextView) findViewById(R.id.text_status); 96 mPlugTextView = (TextView) findViewById(R.id.text_plug_events); 97 mAutoTextView = (TextView) findViewById(R.id.text_log); 98 mAutoTextView.setMovementMethod(new ScrollingMovementMethod()); 99 100 mStartButton = (Button) findViewById(R.id.button_start); 101 mStopButton = (Button) findViewById(R.id.button_stop); 102 mShareButton = (Button) findViewById(R.id.button_share); 103 mShareButton.setEnabled(false); 104 mFailButton = (Button) findViewById(R.id.button_fail); 105 mSkipButton = (Button) findViewById(R.id.button_skip); 106 updateStartStopButtons(false); 107 updateFailSkipButton(false); 108 } 109 updateStartStopButtons(boolean running)110 private void updateStartStopButtons(boolean running) { 111 mStartButton.setEnabled(!running); 112 mStopButton.setEnabled(running); 113 } 114 115 @Override onStart()116 protected void onStart() { 117 super.onStart(); 118 setActivityType(ACTIVITY_TEST_DISCONNECT); 119 } 120 121 @Override isOutput()122 boolean isOutput() { 123 return true; 124 } 125 126 @Override setupEffects(int sessionId)127 public void setupEffects(int sessionId) { 128 } 129 updateFailSkipButton(final boolean running)130 private void updateFailSkipButton(final boolean running) { 131 runOnUiThread(new Runnable() { 132 @Override 133 public void run() { 134 mFailButton.setEnabled(running); 135 mSkipButton.setEnabled(running); 136 } 137 }); 138 } 139 140 // Write to scrollable TextView log(final String text)141 private void log(final String text) { 142 runOnUiThread(new Runnable() { 143 @Override 144 public void run() { 145 mAutoTextView.append(text); 146 mAutoTextView.append("\n"); 147 } 148 }); 149 } 150 151 // Write to status and command view setInstructionsText(final String text)152 private void setInstructionsText(final String text) { 153 runOnUiThread(new Runnable() { 154 @Override 155 public void run() { 156 mInstructionsTextView.setText(text); 157 } 158 }); 159 } 160 161 // Write to status and command view setStatusText(final String text)162 private void setStatusText(final String text) { 163 runOnUiThread(new Runnable() { 164 @Override 165 public void run() { 166 mStatusTextView.setText(text); 167 } 168 }); 169 } 170 logClear()171 private void logClear() { 172 runOnUiThread(new Runnable() { 173 @Override 174 public void run() { 175 mAutoTextView.setText(""); 176 } 177 }); 178 } 179 180 @Override onResume()181 public void onResume() { 182 super.onResume(); 183 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); 184 this.registerReceiver(mPluginReceiver, filter); 185 } 186 187 @Override onPause()188 public void onPause() { 189 this.unregisterReceiver(mPluginReceiver); 190 super.onPause(); 191 } 192 193 // Only call from UI thread. onTestFinished()194 public void onTestFinished() { 195 updateStartStopButtons(false); 196 mShareButton.setEnabled(true); 197 } 198 startAudioTest()199 public void startAudioTest() throws IOException { 200 openAudio(); 201 startAudio(); 202 } 203 stopAudioTest()204 public void stopAudioTest() { 205 stopAudioQuiet(); 206 closeAudio(); 207 } 208 onCancel(View view)209 public void onCancel(View view) { 210 stopAudioTest(); 211 onTestFinished(); 212 } 213 214 // Called on UI thread onStopAudioTest(View view)215 public void onStopAudioTest(View view) { 216 stopAudioTest(); 217 onTestFinished(); 218 keepScreenOn(false); 219 } 220 onStartDisconnectTest(View view)221 public void onStartDisconnectTest(View view) { 222 updateStartStopButtons(true); 223 mThreadEnabled = true; 224 mAutoThread = new Thread(this); 225 mAutoThread.start(); 226 } 227 onStopDisconnectTest(View view)228 public void onStopDisconnectTest(View view) { 229 try { 230 if (mAutoThread != null) { 231 mThreadEnabled = false; 232 mAutoThread.interrupt(); 233 mAutoThread.join(100); 234 mAutoThread = null; 235 } 236 } catch (InterruptedException e) { 237 e.printStackTrace(); 238 } 239 } 240 onFailTest(View view)241 public void onFailTest(View view) { 242 mTestFailed = true; 243 } 244 onSkipTest(View view)245 public void onSkipTest(View view) { 246 mSkipTest = true; 247 } 248 249 // Share text from log via GMail, Drive or other method. onShareResult(View view)250 public void onShareResult(View view) { 251 Intent sharingIntent = new Intent(Intent.ACTION_SEND); 252 sharingIntent.setType("text/plain"); 253 254 String subjectText = "OboeTester Test Disconnect result " + getTimestampString(); 255 sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subjectText); 256 257 String shareBody = mAutoTextView.getText().toString(); 258 sharingIntent.putExtra(Intent.EXTRA_TEXT, shareBody); 259 260 startActivity(Intent.createChooser(sharingIntent, "Share using:")); 261 } 262 getConfigText(StreamConfiguration config)263 private String getConfigText(StreamConfiguration config) { 264 return ((config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) ? "OUT" : "IN") 265 + ", Perf = " + StreamConfiguration.convertPerformanceModeToText( 266 config.getPerformanceMode()) 267 + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode()); 268 } 269 testConfiguration(boolean isInput, int perfMode, int sharingMode, int channelCount, boolean requestPlugin)270 private void testConfiguration(boolean isInput, 271 int perfMode, 272 int sharingMode, 273 int channelCount, 274 boolean requestPlugin) throws InterruptedException { 275 String actualConfigText = "none"; 276 mSkipTest = false; 277 278 AudioInputTester mAudioInTester = null; 279 AudioOutputTester mAudioOutTester = null; 280 281 clearStreamContexts(); 282 283 if (isInput) { 284 mAudioInTester = addAudioInputTester(); 285 } else { 286 mAudioOutTester = addAudioOutputTester(); 287 } 288 289 // Configure settings 290 StreamConfiguration requestedConfig = (isInput) 291 ? mAudioInTester.requestedConfiguration 292 : mAudioOutTester.requestedConfiguration; 293 StreamConfiguration actualConfig = (isInput) 294 ? mAudioInTester.actualConfiguration 295 : mAudioOutTester.actualConfiguration; 296 297 requestedConfig.reset(); 298 requestedConfig.setPerformanceMode(perfMode); 299 requestedConfig.setSharingMode(sharingMode); 300 requestedConfig.setChannelCount(channelCount); 301 302 log("========================== #" + mTestCount); 303 log("Requested:"); 304 log(getConfigText(requestedConfig)); 305 306 // Give previous stream time to close and release resources. Avoid race conditions. 307 Thread.sleep(SETTLING_TIME_MILLIS); 308 if (!mThreadEnabled) return; 309 boolean openFailed = false; 310 AudioStreamBase stream = null; 311 try { 312 startAudioTest(); // this will fill in actualConfig 313 log("Actual:"); 314 actualConfigText = getConfigText(actualConfig) 315 + ", " + (actualConfig.isMMap() ? "MMAP" : "Legacy"); 316 log(actualConfigText); 317 318 stream = (isInput) 319 ? mAudioInTester.getCurrentAudioStream() 320 : mAudioOutTester.getCurrentAudioStream(); 321 } catch (IOException e) { 322 openFailed = true; 323 log(e.getMessage()); 324 } 325 326 // The test is only worth running if we got the configuration we requested. 327 boolean valid = true; 328 if (!openFailed) { 329 if(actualConfig.getSharingMode() != sharingMode) { 330 log("did not get requested sharing mode"); 331 valid = false; 332 } 333 if (actualConfig.getPerformanceMode() != perfMode) { 334 log("did not get requested performance mode"); 335 valid = false; 336 } 337 if (actualConfig.getNativeApi() == StreamConfiguration.NATIVE_API_OPENSLES) { 338 log("OpenSL ES does not support automatic disconnect"); 339 valid = false; 340 } 341 } 342 343 int oldPlugCount = mPlugCount; 344 if (!openFailed && valid) { 345 mTestFailed = false; 346 updateFailSkipButton(true); 347 // poll for stream disconnected 348 while (!mTestFailed && mThreadEnabled && !mSkipTest && 349 stream.getState() == StreamConfiguration.STREAM_STATE_STARTING) { 350 Thread.sleep(POLL_DURATION_MILLIS); 351 } 352 String message = (requestPlugin ? "Plug IN" : "UNplug") + " headset now!"; 353 setStatusText("Testing:\n" + actualConfigText); 354 setInstructionsText(message); 355 int timeoutCount = 0; 356 // Wait for Java plug count to change or stream to disconnect. 357 while (!mTestFailed && mThreadEnabled && !mSkipTest && 358 stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) { 359 Thread.sleep(POLL_DURATION_MILLIS); 360 if (mPlugCount > oldPlugCount) { 361 timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS; 362 break; 363 } 364 } 365 // Wait for timeout or stream to disconnect. 366 while (!mTestFailed && mThreadEnabled && !mSkipTest && (timeoutCount > 0) && 367 stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) { 368 Thread.sleep(POLL_DURATION_MILLIS); 369 timeoutCount--; 370 if (timeoutCount == 0) { 371 mTestFailed = true; 372 } else { 373 setStatusText("Plug detected by Java.\nCounting down to Oboe failure: " + timeoutCount); 374 } 375 } 376 setStatusText(mTestFailed ? "Failed" : "Passed - detected"); 377 } 378 updateFailSkipButton(false); 379 setInstructionsText("Wait..."); 380 381 if (!openFailed) { 382 stopAudioTest(); 383 } 384 385 if (mSkipTest) valid = false; 386 387 if (valid) { 388 if (openFailed) { 389 mFailedSummary.append("------ #" + mTestCount); 390 mFailedSummary.append("\n"); 391 mFailedSummary.append(getConfigText(requestedConfig)); 392 mFailedSummary.append("\n"); 393 mFailedSummary.append("Open failed!\n"); 394 mFailCount++; 395 } else { 396 log("Result:"); 397 boolean passed = !mTestFailed; 398 String resultText = requestPlugin ? "plugIN" : "UNplug"; 399 resultText += ", " + (passed ? TEXT_PASS : TEXT_FAIL); 400 log(resultText); 401 if (!passed) { 402 mFailedSummary.append("------ #" + mTestCount); 403 mFailedSummary.append("\n"); 404 mFailedSummary.append(" "); 405 mFailedSummary.append(actualConfigText); 406 mFailedSummary.append("\n"); 407 mFailedSummary.append(" "); 408 mFailedSummary.append(resultText); 409 mFailedSummary.append("\n"); 410 mFailCount++; 411 } else { 412 mPassCount++; 413 } 414 } 415 } else { 416 log(TEXT_SKIP); 417 } 418 // Give hardware time to settle between tests. 419 Thread.sleep(1000); 420 mTestCount++; 421 } 422 testConfiguration(boolean isInput, int performanceMode, int sharingMode)423 private void testConfiguration(boolean isInput, int performanceMode, 424 int sharingMode) throws InterruptedException { 425 int channelCount = 2; 426 boolean requestPlugin = true; // plug IN 427 testConfiguration(isInput, performanceMode, sharingMode, channelCount, requestPlugin); 428 requestPlugin = false; // UNplug 429 testConfiguration(isInput, performanceMode, sharingMode, channelCount, requestPlugin); 430 } 431 testConfiguration(int performanceMode, int sharingMode)432 private void testConfiguration(int performanceMode, 433 int sharingMode) throws InterruptedException { 434 testConfiguration(false, performanceMode, sharingMode); 435 testConfiguration(true, performanceMode, sharingMode); 436 } 437 438 @Override run()439 public void run() { 440 mPlugCount = 0; 441 logClear(); 442 log("=== STARTED at " + new Date()); 443 log(Build.MANUFACTURER + " " + Build.PRODUCT); 444 log(Build.DISPLAY); 445 mFailedSummary = new StringBuffer(); 446 mTestCount = 0; 447 mPassCount = 0; 448 mFailCount = 0; 449 // Try several different configurations. 450 try { 451 testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, 452 StreamConfiguration.SHARING_MODE_EXCLUSIVE); 453 testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, 454 StreamConfiguration.SHARING_MODE_SHARED); 455 testConfiguration(StreamConfiguration.PERFORMANCE_MODE_NONE, 456 StreamConfiguration.SHARING_MODE_SHARED); 457 } catch (InterruptedException e) { 458 e.printStackTrace(); 459 } finally { 460 stopAudioTest(); 461 setInstructionsText("See summary below."); 462 setStatusText("Finished."); 463 log("\n==== SUMMARY ========"); 464 if (mFailCount > 0) { 465 log(mPassCount + " passed. " + mFailCount + " failed."); 466 log("These tests FAILED:"); 467 log(mFailedSummary.toString()); 468 } else { 469 log("All tests PASSED."); 470 } 471 log("== FINISHED at " + new Date()); 472 runOnUiThread(new Runnable() { 473 @Override 474 public void run() { 475 onTestFinished(); 476 } 477 }); 478 updateFailSkipButton(false); 479 } 480 } 481 482 } 483