1 /*
2  * Copyright (C) 2015 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.app.AlertDialog;
20 import android.media.AudioDeviceCallback;
21 import android.media.AudioDeviceInfo;
22 import android.media.AudioManager;
23 import android.media.MediaRecorder;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Message;
27 import android.util.Log;
28 import android.view.View;
29 import android.view.View.OnClickListener;
30 import android.view.ViewGroup;
31 import android.widget.Button;
32 import android.widget.ProgressBar;
33 import android.widget.SeekBar;
34 import android.widget.TextView;
35 
36 import com.android.compatibility.common.util.ResultType;
37 import com.android.compatibility.common.util.ResultUnit;
38 import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
39 import com.android.cts.verifier.audio.audiolib.StatUtils;
40 import com.android.cts.verifier.audio.audiolib.AudioUtils;
41 import com.android.cts.verifier.CtsVerifierReportLog;
42 import com.android.cts.verifier.PassFailButtons;
43 import com.android.cts.verifier.R;
44 
45 import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
46 import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
47 
48 /**
49  * CtsVerifier Audio Loopback Latency Test
50  */
51 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
52     private static final String TAG = "AudioLoopbackLatencyActivity";
53 
54     // JNI load
55     static {
56         try {
57             System.loadLibrary("audioloopback_jni");
58         } catch (UnsatisfiedLinkError e) {
59             Log.e(TAG, "Error loading Audio Loopback JNI library");
60             Log.e(TAG, "e: " + e);
61             e.printStackTrace();
62         }
63 
64         /* TODO: gracefully fail/notify if the library can't be loaded */
65     }
66     protected AudioManager mAudioManager;
67 
68     // UI
69     TextView mInputDeviceTxt;
70     TextView mOutputDeviceTxt;
71 
72     TextView mAudioLevelText;
73     SeekBar mAudioLevelSeekbar;
74 
75     TextView mTestPathTxt;
76 
77     TextView mResultText;
78     ProgressBar mProgressBar;
79     int mMaxLevel;
80 
81     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
82     protected Button mTestButton;
83 
84     String mYesString;
85     String mNoString;
86 
87     // These flags determine the maximum allowed latency
88     private boolean mClaimsProAudio;
89     private boolean mClaimsOutput;
90     private boolean mClaimsInput;
91 
92     // Useful info
93     private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
94     private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
95 
96     // Peripheral(s)
97     boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
98     AudioDeviceInfo mOutputDevInfo;
99     AudioDeviceInfo mInputDevInfo;
100 
101     protected static final int TESTPERIPHERAL_INVALID       = -1;
102     protected static final int TESTPERIPHERAL_NONE          = 0;
103     protected static final int TESTPERIPHERAL_ANALOG_JACK   = 1;
104     protected static final int TESTPERIPHERAL_USB           = 2;
105     protected static final int TESTPERIPHERAL_DEVICE        = 3; // device speaker + mic
106     protected int mTestPeripheral = TESTPERIPHERAL_NONE;
107 
108     // Loopback Logic
109     NativeAnalyzerThread mNativeAnalyzerThread = null;
110 
111     protected static final int NUM_TEST_PHASES = 5;
112     protected int mTestPhase = 0;
113 
114     protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
115     protected double[] mConfidence = new double[NUM_TEST_PHASES];
116 
117     protected double mMeanLatencyMillis;
118     protected double mMeanAbsoluteDeviation;
119     protected double mMeanConfidence;
120 
121     protected static final double CONFIDENCE_THRESHOLD = 0.6;
122     // impossibly low latencies (indicating something in the test went wrong).
123     protected static final float EPSILON = 1.0f;
124     protected static final double PROAUDIO_RECOMMENDED_LATENCY_MS = 20.0;
125     protected static final double PROAUDIO_RECOMMENDED_USB_LATENCY_MS = 25.0;
126     protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
127     protected static final double BASIC_RECOMMENDED_LATENCY_MS = 50.0;
128     protected static final double BASIC_MUST_LATENCY_MS = 800.0;
129     protected double mMustLatency;
130     protected double mRecommendedLatency;
131 
132     // The audio stream callback threads should stop and close
133     // in less than a few hundred msec. This is a generous timeout value.
134     private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
135 
136     //
137     // Common UI Handling
138     //
connectLoopbackUI()139     private void connectLoopbackUI() {
140         // Connected Device
141         mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
142         mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
143 
144         mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
145         mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
146         mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
147         mAudioLevelSeekbar.setMax(mMaxLevel);
148         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
149         refreshLevel();
150 
151         mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
152             @Override
153             public void onStopTrackingTouch(SeekBar seekBar) {}
154 
155             @Override
156             public void onStartTrackingTouch(SeekBar seekBar) {}
157 
158             @Override
159             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
160                 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
161                         progress, 0);
162                 Log.i(TAG,"Level set to: " + progress);
163                 refreshLevel();
164             }
165         });
166 
167         mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
168         mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
169         showWait(false);
170     }
171 
172     //
173     // Peripheral Connection Logic
174     //
scanPeripheralList(AudioDeviceInfo[] devices)175     protected void scanPeripheralList(AudioDeviceInfo[] devices) {
176         // CDD Section C-1-3: USB port, host-mode support
177 
178         // Can't just use the first record because then we will only get
179         // Source OR sink, not both even on devices that are both.
180         mOutputDevInfo = null;
181         mInputDevInfo = null;
182 
183         // Any valid peripherals
184         // Do we leave in the Headset test to support a USB-Dongle?
185         for (AudioDeviceInfo devInfo : devices) {
186             if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
187                     devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
188                     devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
189                     devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
190                 if (devInfo.isSink()) {
191                     mOutputDevInfo = devInfo;
192                 }
193                 if (devInfo.isSource()) {
194                     mInputDevInfo = devInfo;
195                 }
196             }  else {
197                 handleDeviceConnection(devInfo);
198             }
199         }
200 
201         // need BOTH input and output to test
202         mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
203         calculateTestPeripheral();
204         showConnectedAudioPeripheral();
205         calculateLatencyThresholds();
206         displayLatencyThresholds();
207     }
208 
handleDeviceConnection(AudioDeviceInfo deviceInfo)209     protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
210         // NOP
211     }
212 
213     private class ConnectListener extends AudioDeviceCallback {
ConnectListener()214         /*package*/ ConnectListener() {}
215 
216         //
217         // AudioDevicesManager.OnDeviceConnectionListener
218         //
219         @Override
onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)220         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
221             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
222         }
223 
224         @Override
onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)225         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
226             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
227         }
228     }
229 
calculateTestPeripheral()230     private void calculateTestPeripheral() {
231         if (!mIsPeripheralAttached) {
232             if ((mOutputDevInfo != null && mInputDevInfo == null)
233                 || (mOutputDevInfo == null && mInputDevInfo != null)) {
234                 mTestPeripheral = TESTPERIPHERAL_INVALID;
235             } else {
236                 mTestPeripheral = TESTPERIPHERAL_DEVICE;
237             }
238         } else if (!areIODevicesOnePeripheral()) {
239             mTestPeripheral = TESTPERIPHERAL_INVALID;
240         } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
241                 mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
242             mTestPeripheral = TESTPERIPHERAL_USB;
243         } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
244                 mInputDevInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) {
245             mTestPeripheral = TESTPERIPHERAL_ANALOG_JACK;
246         } else {
247             // Huh?
248             Log.e(TAG, "No valid peripheral found!?");
249             mTestPeripheral = TESTPERIPHERAL_NONE;
250         }
251     }
252 
isPeripheralValidForTest()253     private boolean isPeripheralValidForTest() {
254         return mTestPeripheral == TESTPERIPHERAL_ANALOG_JACK
255                     || mTestPeripheral == TESTPERIPHERAL_USB;
256     }
257 
showConnectedAudioPeripheral()258     private void showConnectedAudioPeripheral() {
259         mInputDeviceTxt.setText(
260                 mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
261                         : "Not connected");
262         mOutputDeviceTxt.setText(
263                 mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
264                         : "Not connected");
265 
266         String pathName;
267         switch (mTestPeripheral) {
268             case TESTPERIPHERAL_INVALID:
269                 pathName = "Invalid Test Peripheral";
270                 break;
271 
272             case TESTPERIPHERAL_ANALOG_JACK:
273                 pathName = "Headset Jack";
274                 break;
275 
276             case TESTPERIPHERAL_USB:
277                 pathName = "USB";
278                 break;
279 
280             case TESTPERIPHERAL_DEVICE:
281                 pathName = "Device Speaker + Microphone";
282                 break;
283 
284             case TESTPERIPHERAL_NONE:
285             default:
286                 pathName = "Error. Unknown Test Path";
287                 break;
288         }
289         mTestPathTxt.setText(pathName);
290         mTestButton.setEnabled(
291                 mTestPeripheral != TESTPERIPHERAL_INVALID && mTestPeripheral != TESTPERIPHERAL_NONE);
292 
293     }
294 
areIODevicesOnePeripheral()295     private boolean areIODevicesOnePeripheral() {
296         if (mOutputDevInfo == null || mInputDevInfo == null) {
297             return false;
298         }
299 
300         return mOutputDevInfo.getProductName().toString().equals(
301                 mInputDevInfo.getProductName().toString());
302     }
303 
calculateLatencyThresholds()304     private void calculateLatencyThresholds() {
305         switch (mTestPeripheral) {
306             case TESTPERIPHERAL_ANALOG_JACK:
307                 mRecommendedLatency = mClaimsProAudio
308                         ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
309                 mMustLatency =  mClaimsProAudio
310                         ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_MUST_LATENCY_MS;
311                 break;
312 
313             case TESTPERIPHERAL_USB:
314                 mRecommendedLatency = mClaimsProAudio
315                         ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
316                 mMustLatency = mClaimsProAudio
317                         ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_MUST_LATENCY_MS;
318                 break;
319 
320             case TESTPERIPHERAL_DEVICE:
321                 // This isn't a valid case so we won't pass it, but it can be run
322                 mRecommendedLatency = mClaimsProAudio
323                         ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
324                 mMustLatency = mClaimsProAudio
325                         ? PROAUDIO_RECOMMENDED_LATENCY_MS :BASIC_MUST_LATENCY_MS;
326                 break;
327 
328             case TESTPERIPHERAL_NONE:
329             default:
330                 mRecommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
331                 mMustLatency = BASIC_MUST_LATENCY_MS;
332                 break;
333         }
334     }
335 
displayLatencyThresholds()336     private void displayLatencyThresholds() {
337         if (isPeripheralValidForTest()) {
338             ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText("" + mMustLatency);
339             ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(
340                     "" + mRecommendedLatency);
341         } else {
342             String naStr = getResources().getString(R.string.audio_proaudio_NA);
343             ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText(naStr);
344             ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(naStr);
345         }
346     }
347 
348     /**
349      * refresh Audio Level seekbar and text
350      */
refreshLevel()351     private void refreshLevel() {
352         int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
353         mAudioLevelSeekbar.setProgress(currentLevel);
354 
355         String levelText = String.format("%s: %d/%d",
356                 getResources().getString(R.string.audio_loopback_level_text),
357                 currentLevel, mMaxLevel);
358         mAudioLevelText.setText(levelText);
359     }
360 
361     //
362     // show active progress bar
363     //
showWait(boolean show)364     protected void showWait(boolean show) {
365         mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
366     }
367 
368     //
369     // Common logging
370     //
371     // Schema
372     private static final String KEY_LATENCY = "latency";
373     private static final String KEY_CONFIDENCE = "confidence";
374     private static final String KEY_SAMPLE_RATE = "sample_rate";
375     private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
376     private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
377     private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
378     private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
379     private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
380     private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
381     private static final String KEY_TEST_MMAP = "supports_mmap";
382     private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
383     private static final String KEY_LEVEL = "level";
384     private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
385 
386     @Override
getTestId()387     public String getTestId() {
388         return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
389     }
390 
391     @Override
getReportFileName()392     public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
393 
394     @Override
getReportSectionName()395     public final String getReportSectionName() {
396         return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
397     }
398 
399     //
400     // Subclasses should call this explicitly. SubClasses should call submit() after their logs
401     //
402     @Override
recordTestResults()403     public void recordTestResults() {
404         if (mNativeAnalyzerThread == null) {
405             return; // no results to report
406         }
407 
408         CtsVerifierReportLog reportLog = getReportLog();
409         reportLog.addValue(
410                 KEY_LATENCY,
411                 mMeanLatencyMillis,
412                 ResultType.LOWER_BETTER,
413                 ResultUnit.MS);
414 
415         reportLog.addValue(
416                 KEY_CONFIDENCE,
417                 mMeanConfidence,
418                 ResultType.HIGHER_BETTER,
419                 ResultUnit.NONE);
420 
421         reportLog.addValue(
422                 KEY_SAMPLE_RATE,
423                 mNativeAnalyzerThread.getSampleRate(),
424                 ResultType.NEUTRAL,
425                 ResultUnit.NONE);
426 
427         reportLog.addValue(
428                 KEY_IS_LOW_LATENCY,
429                 mNativeAnalyzerThread.isLowLatencyStream(),
430                 ResultType.NEUTRAL,
431                 ResultUnit.NONE);
432 
433         reportLog.addValue(
434                 KEY_IS_PERIPHERAL_ATTACHED,
435                 mIsPeripheralAttached,
436                 ResultType.NEUTRAL,
437                 ResultUnit.NONE);
438 
439         reportLog.addValue(
440                 KEY_IS_PRO_AUDIO,
441                 mClaimsProAudio,
442                 ResultType.NEUTRAL,
443                 ResultUnit.NONE);
444 
445         reportLog.addValue(
446                 KEY_TEST_PERIPHERAL,
447                 mTestPeripheral,
448                 ResultType.NEUTRAL,
449                 ResultUnit.NONE);
450 
451         reportLog.addValue(
452                 KEY_TEST_MMAP,
453                 mSupportsMMAP,
454                 ResultType.NEUTRAL,
455                 ResultUnit.NONE);
456 
457         reportLog.addValue(
458                 KEY_TEST_MMAPEXCLUSIVE ,
459                 mSupportsMMAPExclusive,
460                 ResultType.NEUTRAL,
461                 ResultUnit.NONE);
462 
463         if (mIsPeripheralAttached) {
464             reportLog.addValue(
465                     KEY_INPUT_PERIPHERAL_NAME,
466                     mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
467                     ResultType.NEUTRAL,
468                     ResultUnit.NONE);
469 
470             reportLog.addValue(
471                     KEY_OUTPUT_PERIPHERAL_NAME,
472                     mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
473                     ResultType.NEUTRAL,
474                     ResultUnit.NONE);
475         }
476 
477         int audioLevel = mAudioLevelSeekbar.getProgress();
478         reportLog.addValue(
479                 KEY_LEVEL,
480                 audioLevel,
481                 ResultType.NEUTRAL,
482                 ResultUnit.NONE);
483 
484         reportLog.submit();
485     }
486 
startAudioTest(Handler messageHandler)487     private void startAudioTest(Handler messageHandler) {
488         getPassButton().setEnabled(false);
489 
490         mTestPhase = 0;
491         java.util.Arrays.fill(mLatencyMillis, 0.0);
492         java.util.Arrays.fill(mConfidence, 0.0);
493 
494         mNativeAnalyzerThread = new NativeAnalyzerThread(this);
495         if (mNativeAnalyzerThread != null) {
496             mNativeAnalyzerThread.setMessageHandler(messageHandler);
497             // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
498             mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
499             startTestPhase();
500         } else {
501             Log.e(TAG, "Couldn't allocate native analyzer thread");
502             mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
503         }
504     }
505 
startTestPhase()506     private void startTestPhase() {
507         if (mNativeAnalyzerThread != null) {
508             mNativeAnalyzerThread.startTest();
509 
510             // what is this for?
511             try {
512                 Thread.sleep(200);
513             } catch (InterruptedException e) {
514                 e.printStackTrace();
515             }
516         }
517     }
518 
handleTestCompletion()519     private void handleTestCompletion() {
520         mMeanLatencyMillis = StatUtils.calculateMean(mLatencyMillis);
521         mMeanAbsoluteDeviation =
522                 StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
523         mMeanConfidence = StatUtils.calculateMean(mConfidence);
524 
525         boolean pass = isPeripheralValidForTest()
526                 && mMeanConfidence >= CONFIDENCE_THRESHOLD
527                 && mMeanLatencyMillis > EPSILON
528                 && mMeanLatencyMillis < mMustLatency;
529 
530         getPassButton().setEnabled(pass);
531 
532         String result;
533         if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
534             result = String.format(
535                     "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
536                     mMeanConfidence, CONFIDENCE_THRESHOLD);
537         } else {
538             result = String.format(
539                     "Test Finished - %s\nMean Latency:%.2f ms (required:%.2f)\n" +
540                             "Mean Absolute Deviation: %.2f\n" +
541                             " Confidence: %.2f\n" +
542                             " Low Latency Path: %s",
543                     (pass ? "PASS" : "FAIL"),
544                     mMeanLatencyMillis,
545                     mMustLatency,
546                     mMeanAbsoluteDeviation,
547                     mMeanConfidence,
548                     mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
549         }
550 
551         // Make sure the test thread is finished. It should already be done.
552         if (mNativeAnalyzerThread != null) {
553             try {
554                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
555             } catch (InterruptedException e) {
556                 e.printStackTrace();
557             }
558         }
559         mResultText.setText(result);
560 
561         recordTestResults();
562 
563         showWait(false);
564         mTestButton.setEnabled(true);
565     }
566 
567     private void handleTestPhaseCompletion() {
568         if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
569             mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
570             mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
571 
572             String result = String.format(
573                     "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
574                     mTestPhase,
575                     mLatencyMillis[mTestPhase],
576                     mConfidence[mTestPhase]);
577 
578             mResultText.setText(result);
579             try {
580                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
581                 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
582             } catch (InterruptedException e) {
583                 e.printStackTrace();
584             }
585 
586             mTestPhase++;
587             if (mTestPhase >= NUM_TEST_PHASES) {
588                 handleTestCompletion();
589             } else {
590                 startTestPhase();
591             }
592         }
593     }
594 
595     /**
596      * handler for messages from audio thread
597      */
598     private Handler mMessageHandler = new Handler() {
599         public void handleMessage(Message msg) {
600             super.handleMessage(msg);
601             switch(msg.what) {
602                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
603                     Log.v(TAG,"got message native rec started!!");
604                     showWait(true);
605                     mResultText.setText(String.format("[phase: %d] - Test Running...",
606                             (mTestPhase + 1)));
607                     break;
608                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
609                     Log.v(TAG,"got message native rec can't start!!");
610                     mResultText.setText("Test Error opening streams.");
611                     handleTestCompletion();
612                     break;
613                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
614                     Log.v(TAG,"got message native rec can't start!!");
615                     mResultText.setText("Test Error while recording.");
616                     handleTestCompletion();
617                     break;
618                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
619                     mResultText.setText("Test FAILED due to errors.");
620                     handleTestCompletion();
621                     break;
622                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
623                     Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
624                     mResultText.setText(String.format("[phase: %d] - Analyzing ...",
625                             mTestPhase + 1));
626                     break;
627                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
628                     Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
629                     handleTestPhaseCompletion();
630                     break;
631                 default:
632                     break;
633             }
634         }
635     };
636 
637     @Override
638     protected void onCreate(Bundle savedInstanceState) {
639         super.onCreate(savedInstanceState);
640 
641         setContentView(R.layout.audio_loopback_latency_activity);
642 
643         setPassFailButtonClickListeners();
644         getPassButton().setEnabled(false);
645         setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
646 
647         mClaimsOutput = AudioSystemFlags.claimsOutput(this);
648         mClaimsInput = AudioSystemFlags.claimsInput(this);
649         mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
650 
651         mYesString = getResources().getString(R.string.audio_general_yes);
652         mNoString = getResources().getString(R.string.audio_general_no);
653 
654         // Pro Audio
655         ((TextView)findViewById(R.id.audio_loopback_pro_audio)).setText(
656                 "" + (mClaimsProAudio ? mYesString : mNoString));
657 
658         // MMAP
659         ((TextView)findViewById(R.id.audio_loopback_mmap)).setText(
660                 "" + (mSupportsMMAP ? mYesString : mNoString));
661         ((TextView)findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
662                 "" + (mSupportsMMAPExclusive ? mYesString : mNoString));
663 
664         // Low Latency
665         ((TextView)findViewById(R.id.audio_loopback_low_latency)).setText(
666                 "" + (AudioSystemFlags.claimsLowLatencyAudio(this) ? mYesString : mNoString));
667 
668         mTestPathTxt = ((TextView)findViewById(R.id.audio_loopback_testpath));
669 
670         mTestButton = (Button)findViewById(R.id.audio_loopback_test_btn);
671         mTestButton.setOnClickListener(mBtnClickListener);
672 
673         mAudioManager = getSystemService(AudioManager.class);
674 
675         mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
676 
677         connectLoopbackUI();
678 
679         calculateLatencyThresholds();
680         displayLatencyThresholds();
681     }
682 
683     private class OnBtnClickListener implements OnClickListener {
684         @Override
685         public void onClick(View v) {
686             switch (v.getId()) {
687                 case R.id.audio_loopback_test_btn:
688                     Log.i(TAG, "audio loopback test");
689                     startAudioTest(mMessageHandler);
690                     break;
691             }
692         }
693     }
694 }
695