• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.os.Bundle;
24 import android.util.Log;
25 import android.view.View;
26 import android.widget.Button;
27 import android.widget.TextView;
28 
29 import com.android.cts.verifier.PassFailButtons;
30 import com.android.cts.verifier.R;
31 
32 // MegaAudio
33 import org.hyphonate.megaaudio.common.BuilderBase;
34 import org.hyphonate.megaaudio.common.Globals;
35 import org.hyphonate.megaaudio.common.StreamBase;
36 import org.hyphonate.megaaudio.common.StreamState;
37 import org.hyphonate.megaaudio.player.AudioSourceProvider;
38 import org.hyphonate.megaaudio.player.OboePlayer;
39 import org.hyphonate.megaaudio.player.PlayerBuilder;
40 import org.hyphonate.megaaudio.player.sources.SilenceAudioSourceProvider;
41 import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
42 import org.hyphonate.megaaudio.recorder.OboeRecorder;
43 import org.hyphonate.megaaudio.recorder.RecorderBuilder;
44 import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
45 
46 import java.util.ArrayList;
47 
48 // @CddTest(requirement = "7.8.2.1/C-1-1,C-1-2,C-1-3,C-1-4,C-2-1")
49 
50 /**
51  * CTS Verifier Test module for AAudio Stream Disconnect events
52  */
53 public class AudioDisconnectActivity
54         extends PassFailButtons.Activity
55         implements View.OnClickListener {
56     private static final String TAG = AudioDisconnectActivity.class.getSimpleName();
57     private static final boolean DEBUG = false;
58 
59     private BroadcastReceiver mPluginReceiver = new PluginBroadcastReceiver();
60 
61     // MegaAudio
62     private OboePlayer mPlayer;
63     private OboeRecorder mRecorder;
64     private StreamBase  mStream;
65 
66     private int mNumExchangeFrames;
67     private int mSystemSampleRate;
68 
69     // UI
70     private TextView mHasPortQueryText;
71     private Button mHasAnalogPortYesBtn;
72     private Button mHasAnalogPortNoBtn;
73 
74     private Button mStartBtn;
75     private Button mStopBtn;
76 
77     private TextView mUserPromptTx;
78     private TextView mDebugMessageTx;
79     private TextView mResultsTx;
80 
81     // Test State
82     private boolean mHasHeadset;
83     private boolean mIsAudioRunning;
84     private volatile int mPlugCount;
85 
86     static {
StreamBase.loadMegaAudioLibrary()87         StreamBase.loadMegaAudioLibrary();
88     }
89 
90     // Lowlatency/not lowlatency
91     // MMAP/not MMAP (legacy i.e. audioflinger).
92     // Shared/Exclusive (not legacy)
93     class TestConfiguration {
94         static final int IO_INPUT = 0;
95         static final int IO_OUTPUT = 1;
96         int mDirection;
97         int mSampleRate;
98         int mNumChannels;
99 
100         static final int OPTION_NONE = 0x00000000;
101         static final int OPTION_LOWLATENCY = 0x00000001;
102         static final int OPTION_EXCLUSIVE = 0x00000002;
103         static final int OPTION_MMAP = 0x00000004;
104         int mOptions;
105 
106         static final int RESULT_NOTTESTED = -1;
107         static final int RESULT_TIMEOUT = -2;
108         static final int RESULT_SKIPPED = -3;
109         static final int RESULT_DETECTED = 0; // i.e. the disconnect notification was received
110 
111         int mInsertPlugResult;
112         int mInsertDisconnectResult;
113         int mRemovalPlugResult;
114         int mRemovalDisconnectResult;
115 
TestConfiguration(int direction, int sampleRate, int numChannels, int options)116         TestConfiguration(int direction, int sampleRate, int numChannels, int options) {
117             mDirection = direction;
118 
119             mSampleRate = sampleRate;
120             mNumChannels = numChannels;
121 
122             mOptions = options;
123 
124             mInsertPlugResult = RESULT_NOTTESTED;
125             mInsertDisconnectResult = RESULT_NOTTESTED;
126             mRemovalPlugResult = RESULT_NOTTESTED;
127             mRemovalDisconnectResult = RESULT_NOTTESTED;
128         }
129 
isLowLatency()130         boolean isLowLatency() {
131             return (mOptions & OPTION_LOWLATENCY) != 0;
132         }
133 
isExclusive()134         boolean isExclusive() {
135             return (mOptions & OPTION_EXCLUSIVE) != 0;
136         }
137 
isMMap()138         boolean isMMap() {
139             return (mOptions & OPTION_MMAP) != 0;
140         }
141 
resultToString(int resultCode)142         static String resultToString(int resultCode) {
143             switch (resultCode) {
144                 case RESULT_NOTTESTED:
145                     return "NT";
146 
147                 case RESULT_TIMEOUT:
148                     return "TO";
149 
150                 case RESULT_SKIPPED:
151                     return "SK";
152 
153                 case RESULT_DETECTED:
154                     return "OK";
155 
156                 default:
157                     return "??";
158             }
159         }
160 
toString()161         public String toString() {
162             StringBuilder sb = new StringBuilder();
163 
164             sb.append("-----------\n");
165             sb.append("" + (mDirection == TestConfiguration.IO_INPUT ? "IN" : "OUT")
166                     + " " + mSampleRate + " " + mNumChannels
167                     + (isLowLatency() ? " LOW" : "")
168                     + (isExclusive() ? " EX" : "")
169                     + (isMMap() ? " MMAP" : "")
170                     + "\n");
171             sb.append("insert:" + resultToString(mInsertPlugResult)
172                     + " result:" + resultToString(mInsertDisconnectResult));
173             sb.append(" remove:" + resultToString(mRemovalPlugResult)
174                     + " result:" + resultToString(mInsertDisconnectResult) + "\n");
175 
176             return sb.toString();
177         }
178 
isPass()179         boolean isPass() {
180             return (mInsertPlugResult == RESULT_DETECTED
181                         || mInsertPlugResult == RESULT_SKIPPED)
182                     && (mInsertDisconnectResult == RESULT_DETECTED
183                         || mInsertDisconnectResult == RESULT_SKIPPED)
184                     && (mRemovalPlugResult == RESULT_DETECTED
185                         || mRemovalPlugResult == RESULT_SKIPPED)
186                     && (mRemovalDisconnectResult == RESULT_DETECTED
187                         || mRemovalDisconnectResult == RESULT_SKIPPED);
188         }
189 
setSkipped()190         void setSkipped() {
191             mInsertPlugResult = RESULT_SKIPPED;
192             mInsertDisconnectResult = RESULT_SKIPPED;
193             mRemovalPlugResult = RESULT_SKIPPED;
194             mRemovalDisconnectResult = RESULT_SKIPPED;
195         }
196     }
197 
198     private ArrayList<TestConfiguration> mTestConfigs = new ArrayList<TestConfiguration>();
199 
setTestConfigs()200     void setTestConfigs() {
201 //        This logic will cover the four main data paths.
202 //        if (isMMapSupported) {
203 //            LOWLATENCY + MMAP + EXCLUSIVE
204 //            LOWLATENCY + MMAP // shared
205 //        }
206 //        LOWLATENCY // legacy
207 //        NONE
208 
209 
210         // Player
211         // mTestConfigs.add(new TestConfiguration(true, false, 41000, 2));
212         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_OUTPUT,
213                 mSystemSampleRate, 2,
214                 TestConfiguration.OPTION_LOWLATENCY));
215         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_OUTPUT,
216                 mSystemSampleRate, 2,
217                 TestConfiguration.OPTION_LOWLATENCY
218                         | TestConfiguration.OPTION_MMAP));
219         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_OUTPUT,
220                 mSystemSampleRate, 2,
221                 TestConfiguration.OPTION_LOWLATENCY
222                         | TestConfiguration.OPTION_MMAP
223                         | TestConfiguration.OPTION_EXCLUSIVE));
224         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_OUTPUT,
225                 mSystemSampleRate, 2,
226                 TestConfiguration.OPTION_NONE));
227 
228         // Recorder
229         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_INPUT,
230                 mSystemSampleRate, 1,
231                 TestConfiguration.OPTION_LOWLATENCY));
232         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_INPUT,
233                 mSystemSampleRate, 1,
234                 TestConfiguration.OPTION_LOWLATENCY
235                         | TestConfiguration.OPTION_MMAP));
236         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_INPUT,
237                 mSystemSampleRate, 1,
238                 TestConfiguration.OPTION_LOWLATENCY
239                         | TestConfiguration.OPTION_MMAP
240                         | TestConfiguration.OPTION_EXCLUSIVE));
241         mTestConfigs.add(new TestConfiguration(TestConfiguration.IO_INPUT,
242                 mSystemSampleRate, 1,
243                 TestConfiguration.OPTION_NONE));
244     }
245 
resetTestConfigs()246     void resetTestConfigs() {
247         for (TestConfiguration testConfig : mTestConfigs) {
248             testConfig.mInsertPlugResult = TestConfiguration.RESULT_NOTTESTED;
249             testConfig.mInsertDisconnectResult = TestConfiguration.RESULT_NOTTESTED;
250             testConfig.mRemovalPlugResult = TestConfiguration.RESULT_NOTTESTED;
251             testConfig.mRemovalDisconnectResult = TestConfiguration.RESULT_NOTTESTED;
252         }
253     }
254 
setTextMessage(TextView textView, String message)255     void setTextMessage(TextView textView, String message) {
256         runOnUiThread(new Runnable() {
257             @Override
258             public void run() {
259                 textView.setText(message);
260             }
261         });
262     }
263 
264     class Tester implements Runnable {
runTest()265         private void runTest() {
266             boolean abortTest = false;
267             int timeoutCount;
268             mPlugCount = 0;
269 
270             for (int testConfigIndex = 0;
271                     testConfigIndex < mTestConfigs.size();
272                     testConfigIndex++) {
273                 TestConfiguration testConfig = mTestConfigs.get(testConfigIndex);
274 
275                 if (testConfig.isMMap() || testConfig.isExclusive()) {
276                     if (!Globals.isMMapSupported()) {
277                         testConfig.setSkipped();
278                         continue;
279                     }
280                 }
281                 startAudio(testConfig);
282 
283                 // Wait for stream to start...
284                 setTextMessage(mUserPromptTx, "Waiting for stream to start.");
285                 try {
286                     int oldPlugCount;
287                     int error;
288 
289                     //
290                     // Wait for Stream to start
291                     //
292                     timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS;
293                     while (!abortTest && timeoutCount-- > 0
294                             && mStream.getStreamState() != StreamState.STARTED) {
295                         setTextMessage(mDebugMessageTx, "Waiting for stream to start. state:"
296                                 + mStream.getStreamState() + " count:" + timeoutCount);
297                         Thread.sleep(POLL_DURATION_MILLIS);
298                     }
299                     if (timeoutCount <= 0) {
300                         setTextMessage(mUserPromptTx, "TIMEOUT waiting for stream to start");
301                         abortTest = true;
302                         break;
303                     }
304 
305                     //
306                     // Prompt for headset connect
307                     //
308                     setTextMessage(mUserPromptTx, "Insert headset now!");
309 
310                     // Wait for plug count to change
311                     oldPlugCount = mPlugCount;
312                     timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS;
313                     while (!abortTest && timeoutCount-- > 0 && mPlugCount == oldPlugCount) {
314                         setTextMessage(mDebugMessageTx, "Waiting for plug event "
315                                 + mPlugCount + ":" + oldPlugCount + " count: " + timeoutCount);
316                         Thread.sleep(POLL_DURATION_MILLIS);
317                     }
318                     if (timeoutCount <= 0) {
319                         setTextMessage(mUserPromptTx, "TIMEOUT waiting for plug event");
320                         testConfig.mInsertPlugResult = TestConfiguration.RESULT_TIMEOUT;
321                         abortTest = true;
322                         break;
323                     }
324 
325                     testConfig.mInsertPlugResult = TestConfiguration.RESULT_DETECTED;
326 
327                     // Wait for stream to disconnect.
328                     timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS;
329                     while (!abortTest && (timeoutCount > 0)
330                             && mStream.getStreamState() == StreamState.STARTED) {
331                         setTextMessage(mDebugMessageTx, "state:" + mStream.getStreamState()
332                                 + " count:" + timeoutCount);
333                         Thread.sleep(POLL_DURATION_MILLIS);
334                         timeoutCount--;
335                     }
336                     if (timeoutCount <= 0) {
337                         setTextMessage(mUserPromptTx, "TIMEOUT waiting for disconnect");
338                         testConfig.mInsertPlugResult = TestConfiguration.RESULT_TIMEOUT;
339                         abortTest = true;
340                         break;
341                     }
342 
343                     error = mStream.getLastErrorCallbackResult();
344                     if (error != OboePlayer.ERROR_DISCONNECTED) {
345                         // Need to address this
346                         abortTest = true;
347                     }
348                     testConfig.mInsertDisconnectResult = TestConfiguration.RESULT_DETECTED;
349 
350                     // need to restart the stream
351                     restartAudio(testConfig);
352 
353                     //
354                     // Prompt for headset Remove
355                     //
356                     setTextMessage(mUserPromptTx, "Remove headset now!");
357 
358                     // Wait for plug count to change
359                     oldPlugCount = mPlugCount;
360                     timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS;
361                     while (!abortTest && timeoutCount-- > 0 && mPlugCount == oldPlugCount) {
362                         setTextMessage(mDebugMessageTx, "Waiting for plug event "
363                                 + mPlugCount + ":" + oldPlugCount + " count: " + timeoutCount);
364                         Thread.sleep(POLL_DURATION_MILLIS);
365                     }
366                     if (timeoutCount <= 0) {
367                         setTextMessage(mUserPromptTx, "TIMEOUT waiting for plug event");
368                         testConfig.mRemovalPlugResult = TestConfiguration.RESULT_TIMEOUT;
369                         abortTest = true;
370                         break;
371                     }
372 
373                     testConfig.mRemovalPlugResult = TestConfiguration.RESULT_DETECTED;
374 
375                     // Wait for stream to disconnect.
376                     timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS;
377                     while (!abortTest && (timeoutCount > 0)
378                             && mStream.getStreamState() == StreamState.STARTED) {
379                         setTextMessage(mDebugMessageTx, "state:" + mStream.getStreamState()
380                                 + " count:" + timeoutCount);
381                         Thread.sleep(POLL_DURATION_MILLIS);
382                         timeoutCount--;
383                     }
384                     if (timeoutCount <= 0) {
385                         setTextMessage(mUserPromptTx, "TIMEOUT waiting for disconnect");
386                         testConfig.mRemovalDisconnectResult = TestConfiguration.RESULT_TIMEOUT;
387                         abortTest = true;
388                         break;
389                     }
390 
391                     error = mStream.getLastErrorCallbackResult();
392                     if (error != OboePlayer.ERROR_DISCONNECTED) {
393                         // Need to address this
394                     }
395                     testConfig.mRemovalDisconnectResult = TestConfiguration.RESULT_DETECTED;
396 
397                 } catch (InterruptedException ex) {
398                     Log.e(TAG, "InterruptedException: " + ex);
399                     abortTest = true;
400                 }
401                 runOnUiThread(new Runnable() {
402                     @Override
403                     public void run() {
404                         showTestResults();
405                     }
406                 });
407                 stopAudio();
408             } // while (true)
409 
410             endTest();
411         }
412 
run()413         public void run() {
414             runTest();
415         }
416     }
417 
418     //
419     // Test Process
420     //
startTest()421     void startTest() {
422         resetTestConfigs();
423 
424         enableTestButtons(false, true);
425 
426         (new Thread(new Tester())).start();
427     }
428 
endTest()429     void endTest() {
430         showTestResults();
431         runOnUiThread(new Runnable() {
432             @Override
433             public void run() {
434                 mDebugMessageTx.setText("");
435                 enableTestButtons(true, false);
436                 boolean passed = calcTestPass();
437                 getPassButton().setEnabled(passed);
438                 String passStr = getResources().getString(
439                         passed ? R.string.audio_general_teststatus_pass
440                                : R.string.audio_general_teststatus_fail);
441                 mUserPromptTx.setText(passStr);
442             }
443         });
444     }
445 
showTestResults()446     void showTestResults() {
447         StringBuilder sb = new StringBuilder();
448 
449         for (TestConfiguration testConfig : mTestConfigs) {
450             if (testConfig.mInsertPlugResult != TestConfiguration.RESULT_NOTTESTED
451                     || testConfig.mRemovalPlugResult != TestConfiguration.RESULT_NOTTESTED) {
452                 sb.append(testConfig.toString());
453             }
454         }
455 
456         runOnUiThread(new Runnable() {
457             @Override
458             public void run() {
459                 mResultsTx.setText(sb.toString());
460             }
461         });
462     }
463 
calcTestPass()464     boolean calcTestPass() {
465         for (TestConfiguration testConfig : mTestConfigs) {
466             if (!testConfig.isPass()) {
467                 return false;
468             }
469         }
470         return true;
471     }
472 
AudioDisconnectActivity()473     public AudioDisconnectActivity() {
474         super();
475     }
476 
477     // Test Phases
478     private static final int TESTPHASE_NONE = -1;
479     private static final int TESTPHASE_WAITFORSTART = 0;
480     private static final int TESTPHASE_WAITFORCONNECT = 1;
481     private int mTestPhase = TESTPHASE_NONE;
482 
483     // Test Parameters
484     public static final int POLL_DURATION_MILLIS = 50;
485     public static final int TIME_TO_FAILURE_MILLIS = 10000;
486 
487     @Override
onCreate(Bundle savedInstanceState)488     protected void onCreate(Bundle savedInstanceState) {
489         setContentView(R.layout.audio_disconnect_activity);
490         super.onCreate(savedInstanceState);
491 
492         setInfoResources(R.string.audio_disconnect_test, R.string.audio_disconnect_info, -1);
493 
494         // Analog Port?
495         mHasPortQueryText = (TextView) findViewById(R.id.analog_headset_query);
496         mHasAnalogPortYesBtn = (Button) findViewById(R.id.headset_analog_port_yes);
497         mHasAnalogPortYesBtn.setOnClickListener(this);
498         mHasAnalogPortNoBtn = (Button) findViewById(R.id.headset_analog_port_no);
499         mHasAnalogPortNoBtn.setOnClickListener(this);
500 
501         (mStartBtn = (Button) findViewById(R.id.connection_start_btn)).setOnClickListener(this);
502         (mStopBtn = (Button) findViewById(R.id.connection_stop_btn)).setOnClickListener(this);
503 
504         mUserPromptTx = (TextView) findViewById(R.id.user_prompt_tx);
505         mDebugMessageTx = (TextView) findViewById(R.id.debug_message_tx);
506         mResultsTx = (TextView) findViewById(R.id.results_tx);
507 
508         setPassFailButtonClickListeners();
509         getPassButton().setEnabled(false);
510 
511         StreamBase.setup(this);
512         mSystemSampleRate = StreamBase.getSystemSampleRate();
513         mNumExchangeFrames = StreamBase.getNumBurstFrames(BuilderBase.TYPE_NONE);
514 
515         setTestConfigs();
516 
517         enableTestButtons(false, false);
518     }
519 
520     @Override
onResume()521     public void onResume() {
522         super.onResume();
523         IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
524         this.registerReceiver(mPluginReceiver, filter);
525     }
526 
527     @Override
onPause()528     public void onPause() {
529         this.unregisterReceiver(mPluginReceiver);
530         super.onPause();
531     }
532 
533     //
534     // PassFailButtons Overrides
535     //
startAudio(TestConfiguration config)536     private boolean startAudio(TestConfiguration config) {
537         Log.i(TAG, "startAudio()...");
538         if (mIsAudioRunning) {
539             stopAudio();
540         }
541 
542         boolean wasMMapEnabled = Globals.isMMapEnabled();
543         Globals.setMMapEnabled(config.isMMap());
544         if (config.mDirection == TestConfiguration.IO_OUTPUT) {
545             AudioSourceProvider sourceProvider = new SilenceAudioSourceProvider();
546             try {
547                 PlayerBuilder playerBuilder = new PlayerBuilder();
548                 playerBuilder.setPerformanceMode(config.isLowLatency()
549                         ? BuilderBase.PERFORMANCE_MODE_LOWLATENCY
550                         : BuilderBase.PERFORMANCE_MODE_NONE);
551                 playerBuilder.setSharingMode(config.isExclusive()
552                                 ? BuilderBase.SHARING_MODE_EXCLUSIVE
553                                 : BuilderBase.SHARING_MODE_SHARED);
554                 playerBuilder.setChannelCount(config.mNumChannels);
555                 playerBuilder.setSampleRate(config.mSampleRate);
556                 playerBuilder.setSourceProvider(sourceProvider);
557                 playerBuilder.setPlayerType(BuilderBase.TYPE_OBOE);
558                 mPlayer = (OboePlayer) playerBuilder.build();
559                 mPlayer.startStream();
560                 mIsAudioRunning = true;
561                 mStream = mPlayer;
562             } catch (PlayerBuilder.BadStateException badStateException) {
563                 Log.e(TAG, "BadStateException: " + badStateException);
564                 mIsAudioRunning = false;
565             }
566         } else {
567             AudioSinkProvider sinkProvider = new NopAudioSinkProvider();
568             try {
569                 RecorderBuilder recorderBuilder = new RecorderBuilder();
570                 recorderBuilder.setRecorderType(BuilderBase.TYPE_OBOE);
571                 recorderBuilder.setAudioSinkProvider(sinkProvider);
572                 recorderBuilder.setChannelCount(config.mNumChannels);
573                 recorderBuilder.setSampleRate(config.mSampleRate);
574                 recorderBuilder.setChannelCount(config.mNumChannels);
575                 recorderBuilder.setNumExchangeFrames(mNumExchangeFrames);
576                 recorderBuilder.setPerformanceMode(config.isLowLatency()
577                         ? BuilderBase.PERFORMANCE_MODE_LOWLATENCY
578                         : BuilderBase.PERFORMANCE_MODE_NONE);
579                 recorderBuilder.setSharingMode(config.isExclusive()
580                         ? BuilderBase.SHARING_MODE_EXCLUSIVE
581                         : BuilderBase.SHARING_MODE_SHARED);
582                 mRecorder = (OboeRecorder) recorderBuilder.build();
583                 mRecorder.startStream();
584                 mIsAudioRunning = true;
585                 mStream = mRecorder;
586             } catch (RecorderBuilder.BadStateException badStateException) {
587                 Log.e(TAG, "BadStateException: " + badStateException);
588                 mIsAudioRunning = false;
589             }
590         }
591         Globals.setMMapEnabled(wasMMapEnabled);
592 
593         Log.i(TAG, "  mIsAudioRunning: " + mIsAudioRunning);
594         return mIsAudioRunning;
595     }
596 
restartAudio(TestConfiguration config)597     private boolean restartAudio(TestConfiguration config) {
598         mIsAudioRunning = false;
599         return startAudio(config);
600     }
601 
stopAudio()602     private void stopAudio() {
603         if (!mIsAudioRunning) {
604             return;
605         }
606 
607         if (mPlayer != null) {
608             mPlayer.stopStream();
609             mPlayer.teardownStream();
610         }
611 
612         if (mRecorder != null) {
613             mRecorder.stopStream();
614             mRecorder.teardownStream();
615         }
616 
617         mIsAudioRunning = false;
618     }
619 
enableTestButtons(boolean start, boolean stop)620     void enableTestButtons(boolean start, boolean stop) {
621         mStartBtn.setEnabled(start);
622         mStopBtn.setEnabled(stop);
623     }
624 
hideHasHeadsetUI()625     void hideHasHeadsetUI() {
626         mHasPortQueryText.setText(getResources().getString(
627                 R.string.analog_headset_port_detected));
628         mHasAnalogPortYesBtn.setVisibility(View.GONE);
629         mHasAnalogPortNoBtn.setVisibility(View.GONE);
630     }
631 
632     //
633     // View.OnClickHandler
634     //
635     @Override
onClick(View view)636     public void onClick(View view) {
637         int id = view.getId();
638         if (id == R.id.headset_analog_port_yes) {
639             enableTestButtons(true, false);
640         } else if (id == R.id.headset_analog_port_no) {
641             String passStr = getResources().getString(
642                     R.string.audio_general_teststatus_pass);
643             mUserPromptTx.setText(passStr);
644             getPassButton().setEnabled(true);
645             enableTestButtons(false, false);
646         } else if (id == R.id.connection_start_btn) {
647             mResultsTx.setText("");
648             startTest();
649         } else if (id == R.id.connection_stop_btn) {
650             stopAudio();
651         }
652     }
653 
654     /**
655      * Receive a broadcast Intent when a headset is plugged in or unplugged.
656      * Display a count on screen.
657      */
658     public class PluginBroadcastReceiver extends BroadcastReceiver {
659         @Override
onReceive(Context context, Intent intent)660         public void onReceive(Context context, Intent intent) {
661             if (!mHasHeadset) {
662                 mHasHeadset = true;
663                 hideHasHeadsetUI();
664                 enableTestButtons(true, false);
665             }
666             mPlugCount++;
667         }
668     }
669 }
670