• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 org.drrickorang.loopback;
18 
19 import android.Manifest;
20 import android.app.Activity;
21 import android.app.DialogFragment;
22 import android.app.Fragment;
23 import android.app.FragmentManager;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.content.pm.PackageManager;
29 import android.database.Cursor;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.hardware.usb.UsbDevice;
33 import android.hardware.usb.UsbManager;
34 import android.media.AudioDeviceInfo;
35 import android.media.AudioManager;
36 import android.net.Uri;
37 import android.os.Build;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.Looper;
42 import android.os.Message;
43 import android.os.ParcelFileDescriptor;
44 import android.provider.MediaStore;
45 import androidx.core.app.ActivityCompat;
46 import androidx.core.content.ContextCompat;
47 import android.text.format.DateFormat;
48 import android.util.Log;
49 import android.view.Gravity;
50 import android.view.Menu;
51 import android.view.MenuInflater;
52 import android.view.MenuItem;
53 import android.view.View;
54 import android.view.ViewGroup;
55 import android.widget.Button;
56 import android.widget.LinearLayout;
57 import android.widget.PopupWindow;
58 import android.widget.SeekBar;
59 import android.widget.TextView;
60 import android.widget.Toast;
61 
62 import java.io.File;
63 import java.io.FileDescriptor;
64 import java.io.FileOutputStream;
65 import java.util.Arrays;
66 import java.util.HashMap;
67 import java.util.Locale;
68 import java.util.Map;
69 
70 
71 /**
72  * This is the main activity of the Loopback app. Two tests (latency test and buffer test) can be
73  * initiated here. Note: buffer test and glitch detection is the same test, it's just that this test
74  * has two parts of result.
75  */
76 
77 public class LoopbackActivity extends Activity
78         implements SaveFilesDialogFragment.NoticeDialogListener {
79     private static final String TAG = "LoopbackActivity";
80 
81     private static final int SAVE_TO_WAVE_REQUEST = 42;
82     private static final int SAVE_TO_PNG_REQUEST = 43;
83     private static final int SAVE_TO_TXT_REQUEST = 44;
84     private static final int SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST = 45;
85     private static final int SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST = 46;
86     private static final int SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST = 47;
87     private static final int SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST = 48;
88     private static final int SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST = 49;
89     private static final int SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST = 50;
90     private static final int SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST = 51;
91     private static final int SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST = 52;
92 
93     private static final int SETTINGS_ACTIVITY_REQUEST = 54;
94 
95     private static final int THREAD_SLEEP_DURATION_MS = 200;
96     private static final int PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY = 201;
97     private static final int PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER = 202;
98     private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS = 203;
99     private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT = 204;
100     private static final int LATENCY_TEST_STARTED = 300;
101     private static final int LATENCY_TEST_ENDED = 301;
102     private static final int BUFFER_TEST_STARTED = 302;
103     private static final int BUFFER_TEST_ENDED = 303;
104     private static final int CALIBRATION_STARTED = 304;
105     private static final int CALIBRATION_ENDED = 305;
106     private static final int NO_TEST_ACTIVE = 306;
107 
108     // 0-100 controls compression rate, currently ignore because PNG format is being used
109     private static final int EXPORTED_IMAGE_QUALITY = 100;
110 
111     private static final int HISTOGRAM_EXPORT_WIDTH = 2000;
112     private static final int HISTOGRAM_EXPORT_HEIGHT = 2000;
113     private static final int HEATMAP_DRAW_WIDTH = 2560;
114     private static final int HEATMAP_DRAW_HEIGHT = 1440;
115     private static final int HEATMAP_EXPORT_DIVISOR = 2;
116 
117     LoopbackAudioThread  mAudioThread = null;
118     NativeAudioThread    mNativeAudioThread = null;
119     private Thread       mCalibrationThread;
120     private WavePlotView mWavePlotView;
121     private String       mTestStartTimeString = "IncorrectTime";  // The time the test begins
122     private static final String FILE_SAVE_PATH = "file://mnt/sdcard/";
123 
124     private SeekBar  mBarMasterLevel; // drag the volume
125     private TextView mTextInfo;
126     private TextView mTextViewCurrentLevel;
127     private TextView mTextViewResultSummary;
128 
129     private static final String TAG_RETAINED_FRAGMENT = "RetainedFragment";
130     private RetainedFragment mRetainedFragment;
131     private int              mTestType;
132     private Correlation      mCorrelation = new Correlation();
133     private BufferPeriod     mRecorderBufferPeriod = new BufferPeriod();
134     private BufferPeriod     mPlayerBufferPeriod = new BufferPeriod();
135 
136     // for native buffer period
137     private int[]  mNativeRecorderBufferPeriodArray;
138     private int    mNativeRecorderMaxBufferPeriod;
139     private double mNativeRecorderStdDevBufferPeriod;
140     private int[]  mNativePlayerBufferPeriodArray;
141     private int    mNativePlayerMaxBufferPeriod;
142     private double mNativePlayerStdDevBufferPeriod;
143     private BufferCallbackTimes mRecorderCallbackTimes;
144     private BufferCallbackTimes mPlayerCallbackTimes;
145 
146     private static final String INTENT_SAMPLING_FREQUENCY = "SF";
147     private static final String INTENT_CHANNEL_INDEX = "CI";
148     private static final String INTENT_CORRELATION_BLOCK_SIZE = "BS";
149     private static final String INTENT_FILENAME = "FileName";
150     private static final String INTENT_RECORDER_BUFFER = "RecorderBuffer";
151     private static final String INTENT_PLAYER_BUFFER = "PlayerBuffer";
152     private static final String INTENT_AUDIO_THREAD = "AudioThread";
153     private static final String INTENT_MIC_SOURCE = "MicSource";
154     private static final String INTENT_PERFORMANCE_MODE = "PerformanceMode";
155     private static final String INTENT_AUDIO_LEVEL = "AudioLevel";
156     private static final String INTENT_IGNORE_FIRST_FRAMES = "IgnoreFirstFrames";
157     private static final String INTENT_TEST_TYPE = "TestType";
158     private static final String INTENT_BUFFER_TEST_DURATION = "BufferTestDuration";
159     private static final String INTENT_NUMBER_LOAD_THREADS = "NumLoadThreads";
160     private static final String INTENT_ENABLE_SYSTRACE = "CaptureSysTrace";
161     private static final String INTENT_ENABLE_WAVCAPTURE = "CaptureWavs";
162     private static final String INTENT_NUM_CAPTURES = "NumCaptures";
163     private static final String INTENT_WAV_DURATION = "WavDuration";
164     private static final String INTENT_USB_AUDIO_ROUTE = "USB";
165 
166     // for running the test using adb command
167     private volatile boolean mIntentRunning; // if it is running triggered by intent with parameters
168     private String  mIntentFileName;
169 
170     // Note: these values should only be assigned in restartAudioSystem()
171     private int   mAudioThreadType = Constant.UNKNOWN;
172     private int   mMicSource;
173     private int   mPerformanceMode;
174     private int   mSamplingRate;
175     private int   mChannelIndex;
176     private int   mSoundLevel;
177     private int   mPlayerBufferSizeInBytes;
178     private int   mRecorderBufferSizeInBytes;
179     private int   mIgnoreFirstFrames; // TODO: this only applies to native mode
180     private CaptureHolder mCaptureHolder;
181 
182     // for buffer test
183     private int[]   mGlitchesData;
184     private boolean mGlitchingIntervalTooLong;
185     private int     mFFTSamplingSize;
186     private int     mFFTOverlapSamples;
187     private long    mBufferTestStartTime;
188     private int     mBufferTestElapsedSeconds;
189     private int     mBufferTestDurationInSeconds;
190     private int     mBufferTestWavePlotDurationInSeconds;
191 
192     // CTS Test Flag
193     private boolean mIsCTSTest;
194     private int     mCtsNumIterations;
195 
196     // threads that load CPUs
197     private LoadThread[]     mLoadThreads;
198 
199     // for getting the Service
200     boolean mBound = false;
201     private AudioTestService mAudioTestService;
202     private final ServiceConnection mServiceConnection = new ServiceConnection() {
203         public void onServiceConnected(ComponentName className, IBinder service) {
204             mAudioTestService = ((AudioTestService.AudioTestBinder) service).getService();
205             mBound = true;
206         }
207 
208         public void onServiceDisconnected(ComponentName className) {
209             mAudioTestService = null;
210             mBound = false;
211         }
212     };
213 
214     private Handler mMessageHandler = new Handler(Looper.getMainLooper()) {
215         public void handleMessage(Message msg) {
216             super.handleMessage(msg);
217             switch (msg.what) {
218 
219             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED:
220                 log("got message java latency test started!!");
221                 showToast("Java Latency Test Started");
222                 resetResults();
223                 refreshState();
224                 refreshPlots();
225                 break;
226 
227             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR:
228                 log("got message java latency test rec can't start!!");
229                 showToastImportant("Java Latency Test Recording Error. Please try again");
230                 refreshState();
231                 stopAudioTestThreads();
232                 mIntentRunning = false;
233                 refreshSoundLevelBar();
234                 break;
235 
236             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP:
237             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE:
238                 if (mAudioThread != null) {
239                     mRetainedFragment.setWaveData(mAudioThread.getWaveData());
240                     mRecorderCallbackTimes = mRecorderBufferPeriod.getCallbackTimes();
241                     mPlayerCallbackTimes = mPlayerBufferPeriod.getCallbackTimes();
242                     mCorrelation.computeCorrelation(mRetainedFragment.getWaveData(), mSamplingRate);
243                     log("got message java latency rec complete!!");
244                     refreshPlots();
245                     refreshState();
246 
247                     switch (msg.what) {
248                     case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP:
249                         showToast("Java Latency Test Stopped");
250                         break;
251                     case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE:
252                         showToast("Java Latency Test Completed");
253                         break;
254                     }
255 
256                     stopAudioTestThreads();
257                     if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) {
258                         saveAllTo(mIntentFileName);
259                     }
260                     mIntentRunning = false;
261                 }
262                 refreshSoundLevelBar();
263                 break;
264 
265             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED:
266                 log("got message java buffer test rec started!!");
267                 showToast("Java Buffer Test Started");
268                 resetResults();
269                 refreshState();
270                 refreshPlots();
271                 mBufferTestStartTime = System.currentTimeMillis();
272                 break;
273 
274             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR:
275                 log("got message java buffer test rec can't start!!");
276                 showToastImportant("Java Buffer Test Recording Error. Please try again");
277                 refreshState();
278                 stopAudioTestThreads();
279                 mIntentRunning = false;
280                 refreshSoundLevelBar();
281                 break;
282 
283             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP:
284             case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE:
285                 if (mAudioThread != null) {
286                     mRetainedFragment.setWaveData(mAudioThread.getWaveData());
287                     mGlitchesData = mAudioThread.getAllGlitches();
288                     mGlitchingIntervalTooLong = mAudioThread.getGlitchingIntervalTooLong();
289                     mFFTSamplingSize = mAudioThread.getFFTSamplingSize();
290                     mFFTOverlapSamples = mAudioThread.getFFTOverlapSamples();
291                     mRecorderCallbackTimes = mRecorderBufferPeriod.getCallbackTimes();
292                     mPlayerCallbackTimes = mPlayerBufferPeriod.getCallbackTimes();
293                     refreshPlots();  // only plot that last few seconds
294                     refreshState();
295                     //rounded up number of seconds elapsed
296                     mBufferTestElapsedSeconds =
297                             (int) ((System.currentTimeMillis() - mBufferTestStartTime +
298                             Constant.MILLIS_PER_SECOND - 1) / Constant.MILLIS_PER_SECOND);
299                     switch (msg.what) {
300                     case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP:
301                         showToast("Java Buffer Test Stopped");
302                         break;
303                     case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE:
304                         showToast("Java Buffer Test Completed");
305                         break;
306                     }
307                     if (getApp().isCaptureEnabled()) {
308                         mCaptureHolder.stopLoopbackListenerScript();
309                     }
310                     stopAudioTestThreads();
311                     if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) {
312                         saveAllTo(mIntentFileName);
313                     }
314                     mIntentRunning = false;
315                 }
316                 refreshSoundLevelBar();
317                 break;
318 
319             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED:
320                 showToast("Native Latency Test Started");
321                 log("got message native latency test rec started!!");
322                 resetResults();
323                 refreshState();
324                 refreshPlots();
325                 break;
326 
327             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED:
328                 log("got message native buffer test rec started!!");
329                 showToast("Native Buffer Test Started");
330                 resetResults();
331                 refreshState();
332                 refreshPlots();
333                 mBufferTestStartTime = System.currentTimeMillis();
334                 break;
335 
336             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR:
337                 log("got message native latency test rec can't start!!");
338                 showToastImportant("Native Latency Test Recording Error. Please try again");
339                 refreshState();
340                 mIntentRunning = false;
341                 refreshSoundLevelBar();
342                 break;
343 
344             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR:
345                 log("got message native buffer test rec can't start!!");
346                 showToastImportant("Native Buffer Test Recording Error. Please try again");
347                 refreshState();
348                 mIntentRunning = false;
349                 refreshSoundLevelBar();
350                 break;
351 
352             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP:
353             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP:
354             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE:
355             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE:
356             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS:
357             case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS:
358                     if (mNativeAudioThread != null) {
359                     mGlitchesData = mNativeAudioThread.getNativeAllGlitches();
360                     mGlitchingIntervalTooLong = mNativeAudioThread.getGlitchingIntervalTooLong();
361                     mFFTSamplingSize = mNativeAudioThread.getNativeFFTSamplingSize();
362                     mFFTOverlapSamples = mNativeAudioThread.getNativeFFTOverlapSamples();
363                     mRetainedFragment.setWaveData(mNativeAudioThread.getWaveData());
364                     mNativeRecorderBufferPeriodArray = mNativeAudioThread.getRecorderBufferPeriod();
365                     mNativeRecorderMaxBufferPeriod =
366                             mNativeAudioThread.getRecorderMaxBufferPeriod();
367                     mNativeRecorderStdDevBufferPeriod =
368                             mNativeAudioThread.getRecorderStdDevBufferPeriod();
369                     mNativePlayerBufferPeriodArray = mNativeAudioThread.getPlayerBufferPeriod();
370                     mNativePlayerMaxBufferPeriod = mNativeAudioThread.getPlayerMaxBufferPeriod();
371                     mNativePlayerStdDevBufferPeriod =
372                             mNativeAudioThread.getPlayerStdDevBufferPeriod();
373                     mRecorderCallbackTimes = mNativeAudioThread.getRecorderCallbackTimes();
374                     mPlayerCallbackTimes = mNativeAudioThread.getPlayerCallbackTimes();
375 
376                     if (msg.what != NativeAudioThread.
377                             LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE) {
378                         mCorrelation.computeCorrelation(mRetainedFragment.getWaveData(),
379                                 mSamplingRate);
380                     }
381 
382                     log("got message native buffer test rec complete!!");
383                     refreshPlots();
384                     refreshState();
385                     //rounded up number of seconds elapsed
386                     mBufferTestElapsedSeconds =
387                             (int) ((System.currentTimeMillis() - mBufferTestStartTime +
388                                     Constant.MILLIS_PER_SECOND - 1) / Constant.MILLIS_PER_SECOND);
389                     switch (msg.what) {
390                         case NativeAudioThread.
391                                 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS:
392                         case NativeAudioThread.
393                                 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS:
394                         showToastImportant("Native Test Completed with Fatal Errors");
395                         break;
396                         case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP:
397                         case NativeAudioThread.
398                                 LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP:
399                         showToast("Native Test Stopped");
400                         break;
401                     default:
402                         showToast("Native Test Completed");
403                         break;
404                     }
405 
406 
407                     stopAudioTestThreads();
408                     if (mIntentRunning && mIntentFileName != null && mIntentFileName.length() > 0) {
409                         saveAllTo(mIntentFileName);
410                     }
411                     mIntentRunning = false;
412 
413                     if (getApp().isCaptureEnabled()) {
414                         mCaptureHolder.stopLoopbackListenerScript();
415                     }
416                 }  // mNativeAudioThread != null
417                 refreshSoundLevelBar();
418                 break;
419 
420             default:
421                 log("Got message:" + msg.what);
422                 break;
423             }
424 
425             // Control UI elements visibility specific to latency or buffer/glitch test
426             switch (msg.what) {
427 
428                 // Latency test started
429                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED:
430                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STARTED:
431                     setTransportButtonsState(LATENCY_TEST_STARTED);
432                     break;
433 
434                 // Latency test ended
435                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE:
436                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR:
437                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP:
438                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_ERROR:
439                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE:
440                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_STOP:
441                 case NativeAudioThread.
442                         LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_LATENCY_REC_COMPLETE_ERRORS:
443                     setTransportButtonsState(LATENCY_TEST_ENDED);
444                     if (mIsCTSTest) {
445                         Intent intent = getIntent();
446                         intent.putExtra("RoundTripTime", mCorrelation.mEstimatedLatencyMs);
447                         intent.putExtra("Confidence", mCorrelation.mEstimatedLatencyConfidence);
448                         setResult(RESULT_OK, intent);
449                         finish();
450                     }
451                     break;
452 
453                 // Buffer test started
454                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED:
455                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STARTED:
456                     setTransportButtonsState(BUFFER_TEST_STARTED);
457                     break;
458 
459                 // Buffer test ended
460                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE:
461                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR:
462                 case LoopbackAudioThread.LOOPBACK_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP:
463                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_ERROR:
464                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE:
465                 case NativeAudioThread.LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_STOP:
466                 case NativeAudioThread.
467                         LOOPBACK_NATIVE_AUDIO_THREAD_MESSAGE_BUFFER_REC_COMPLETE_ERRORS:
468                     setTransportButtonsState(BUFFER_TEST_ENDED);
469                     break;
470 
471                 // Sound Calibration started
472                 case CALIBRATION_STARTED:
473                     setTransportButtonsState(CALIBRATION_STARTED);
474                     break;
475 
476                 // Sound Calibration ended
477                 case CALIBRATION_ENDED:
478                     setTransportButtonsState(CALIBRATION_ENDED);
479                     break;
480 
481                 default:
482                     log("Got message:" + msg.what);
483                     break;
484             }
485         }
486     };
487 
488     public static class RetainedFragment extends Fragment {
489         private double[] mWaveData;    // this is where we store the data for the wave plot
490 
491         // this method is only called once for this fragment
492         @Override
onCreate(Bundle savedInstanceState)493         public void onCreate(Bundle savedInstanceState) {
494             super.onCreate(savedInstanceState);
495             setRetainInstance(true);
496         }
497 
setWaveData(double[] waveData)498         public void setWaveData(double[] waveData) {
499             this.mWaveData = waveData;
500         }
501 
getWaveData()502         public double[] getWaveData() {
503             return mWaveData;
504         }
505     }
506 
507     @Override
onCreate(Bundle savedInstanceState)508     public void onCreate(Bundle savedInstanceState) {
509         super.onCreate(savedInstanceState);
510 
511         // Set the layout for this activity. You can find it
512         View view = getLayoutInflater().inflate(R.layout.main_activity, null);
513         setContentView(view);
514 
515         // find the retained fragment on activity restarts
516         FragmentManager fm = getFragmentManager();
517         mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(TAG_RETAINED_FRAGMENT);
518         // create the fragment and data the first time
519         if (mRetainedFragment == null) {
520             mRetainedFragment = new RetainedFragment();
521             fm.beginTransaction().add(mRetainedFragment, TAG_RETAINED_FRAGMENT).commit();
522         }
523 
524         // TODO: Write script to file at more appropriate time, from settings activity or intent
525         // TODO: Respond to failure with more than just a toast
526         if (hasWriteFilePermission()) {
527             boolean successfulWrite = AtraceScriptsWriter.writeScriptsToFile(this);
528             if (!successfulWrite) {
529                 showToastImportant("Unable to write loopback_listener script to device");
530             }
531         } else {
532             requestWriteFilePermission(PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT);
533         }
534 
535 
536         mTextInfo = (TextView) findViewById(R.id.textInfo);
537         mBarMasterLevel = (SeekBar) findViewById(R.id.BarMasterLevel);
538 
539         AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
540         int maxVolume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
541         mBarMasterLevel.setMax(maxVolume);
542 
543         mBarMasterLevel.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
544             @Override
545             public void onStopTrackingTouch(SeekBar seekBar) {
546             }
547 
548             @Override
549             public void onStartTrackingTouch(SeekBar seekBar) {
550             }
551 
552             @Override
553             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
554                 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
555                 am.setStreamVolume(AudioManager.STREAM_MUSIC,
556                         progress, 0);
557                 refreshSoundLevelBar();
558                 log("Changed stream volume to: " + progress);
559             }
560         });
561         mWavePlotView = (WavePlotView) findViewById(R.id.viewWavePlot);
562 
563         mTextViewCurrentLevel = (TextView) findViewById(R.id.textViewCurrentLevel);
564         mTextViewCurrentLevel.setTextSize(15);
565 
566         mTextViewResultSummary = (TextView) findViewById(R.id.resultSummary);
567         refreshSoundLevelBar();
568 
569         if (savedInstanceState != null) {
570             restoreInstanceState(savedInstanceState);
571         }
572 
573         if (!hasRecordAudioPermission()) {
574             requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY);
575         }
576 
577         setTransportButtonsState(NO_TEST_ACTIVE);
578 
579         applyIntent(getIntent());
580     }
581 
582     @Override
onStart()583     protected void onStart() {
584         super.onStart();
585         Intent audioTestIntent = new Intent(this, AudioTestService.class);
586         startService(audioTestIntent);
587         boolean bound = bindService(audioTestIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
588         if (bound) {
589             log("Successfully bound to service!");
590         } else {
591             log("Failed to bind service!");
592         }
593     }
594 
595 
596     @Override
onStop()597     protected void onStop() {
598         super.onStop();
599         log("Activity on stop!");
600         // Unbind from the service
601         if (mBound) {
602             unbindService(mServiceConnection);
603             mBound = false;
604         }
605     }
606 
607     @Override
onNewIntent(Intent intent)608     public void onNewIntent(Intent intent) {
609         log("On New Intent called!");
610         applyIntent(intent);
611     }
612 
613 
614     /**
615      * This method will be called whenever the test starts running (either by operating on the
616      * device or by adb command). In the case where the test is started through adb command,
617      * adb parameters will be read into intermediate variables.
618      */
applyIntent(Intent intent)619     private void applyIntent(Intent intent) {
620         Bundle b = intent.getExtras();
621 
622         if (b != null) {
623             for (String key: b.keySet()) {
624                 Log.d (TAG, key + " is a key in the bundle");
625             }
626         }
627 
628         if (b != null && !mIntentRunning) {
629             // adb shell am start -n org.drrickorang.loopback/.LoopbackActivity
630             // --ei SF 48000 --es FileName test1 --ei RecorderBuffer 512 --ei PlayerBuffer 512
631             // --ei AudioThread 1 --ei MicSource 3 --ei AudioLevel 12
632             // --ei TestType 223 --ei BufferTestDuration 60 --ei NumLoadThreads 4
633             // --ei CI -1 --ez CaptureSysTrace true --ez CaptureWavs false --ei NumCaptures 5
634             // --ei WavDuration 15
635 
636             // Note: for native mode, player and recorder buffer sizes are the same, and can only be
637             // set through player buffer size
638 
639             boolean hasRecordAudioPermission = hasRecordAudioPermission();
640             boolean hasWriteFilePermission = hasWriteFilePermission();
641             if (!hasRecordAudioPermission || !hasWriteFilePermission) {
642                 if (!hasRecordAudioPermission) {
643                     log("Missing Permission: RECORD_AUDIO");
644                 }
645 
646                 if (!hasWriteFilePermission) {
647                     log("Missing Permission: WRITE_EXTERNAL_STORAGE");
648                 }
649 
650                 return;
651             }
652 
653             if (b.containsKey(INTENT_AUDIO_THREAD)) {
654                 int newAudioThreadType = b.getInt(INTENT_AUDIO_THREAD);
655                 if (newAudioThreadType != getApp().getAudioThreadType()) {
656                     getApp().setAudioThreadType(newAudioThreadType);
657                     getApp().computeDefaults();
658                 }
659                 mIntentRunning = true;
660             }
661 
662             if (b.containsKey(INTENT_PERFORMANCE_MODE)) {
663                 int newPerformanceMode = b.getInt(INTENT_PERFORMANCE_MODE);
664                 if (newPerformanceMode != getApp().getPerformanceMode()) {
665                     getApp().setPerformanceMode(newPerformanceMode);
666                     getApp().computeDefaults();
667                 }
668                 mIntentRunning = true;
669             }
670 
671             if (b.containsKey(INTENT_BUFFER_TEST_DURATION)) {
672                 getApp().setBufferTestDuration(b.getInt(INTENT_BUFFER_TEST_DURATION));
673                 mIntentRunning = true;
674             }
675 
676             if (b.containsKey(INTENT_SAMPLING_FREQUENCY)) {
677                 getApp().setSamplingRate(b.getInt(INTENT_SAMPLING_FREQUENCY));
678                 mIntentRunning = true;
679             }
680 
681             if (b.containsKey(INTENT_CORRELATION_BLOCK_SIZE)) {
682                 mCorrelation.setBlockSize(b.getInt(INTENT_CORRELATION_BLOCK_SIZE));
683                 mIntentRunning = true;
684             }
685 
686             if (b.containsKey(INTENT_CHANNEL_INDEX)) {
687                 getApp().setChannelIndex(b.getInt(INTENT_CHANNEL_INDEX));
688                 mChannelIndex = b.getInt(INTENT_CHANNEL_INDEX);
689                 mIntentRunning = true;
690             }
691 
692             if (b.containsKey(INTENT_FILENAME)) {
693                 mIntentFileName = b.getString(INTENT_FILENAME);
694                 mIntentRunning = true;
695             }
696 
697             if (b.containsKey(INTENT_RECORDER_BUFFER)) {
698                 getApp().setRecorderBufferSizeInBytes(
699                         b.getInt(INTENT_RECORDER_BUFFER) * Constant.BYTES_PER_FRAME);
700                 mIntentRunning = true;
701             }
702 
703             if (b.containsKey(INTENT_PLAYER_BUFFER)) {
704                 getApp().setPlayerBufferSizeInBytes(
705                         b.getInt(INTENT_PLAYER_BUFFER) * Constant.BYTES_PER_FRAME);
706                 mIntentRunning = true;
707             }
708 
709             if (b.containsKey(INTENT_MIC_SOURCE)) {
710                 getApp().setMicSource(b.getInt(INTENT_MIC_SOURCE));
711                 mIntentRunning = true;
712             }
713 
714             if (b.containsKey(INTENT_IGNORE_FIRST_FRAMES)) {
715                 getApp().setIgnoreFirstFrames(b.getInt(INTENT_IGNORE_FIRST_FRAMES));
716                 mIntentRunning = true;
717             }
718 
719             if (b.containsKey(INTENT_AUDIO_LEVEL)) {
720                 int audioLevel = b.getInt(INTENT_AUDIO_LEVEL);
721                 if (audioLevel >= 0) {
722                     AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
723                     am.setStreamVolume(AudioManager.STREAM_MUSIC, audioLevel, 0);
724                     getApp().setSoundLevelCalibrationEnabled(false);
725                 } else { // AudioLevel of -1 means automatically calibrate
726                     getApp().setSoundLevelCalibrationEnabled(true);
727                 }
728                 mIntentRunning = true;
729             }
730 
731             if (b.containsKey(INTENT_NUMBER_LOAD_THREADS)) {
732                 getApp().setNumberOfLoadThreads(b.getInt(INTENT_NUMBER_LOAD_THREADS));
733                 mIntentRunning = true;
734             }
735 
736             if (b.containsKey(INTENT_ENABLE_SYSTRACE)) {
737                 getApp().setCaptureSysTraceEnabled(b.getBoolean(INTENT_ENABLE_SYSTRACE));
738                 mIntentRunning = true;
739             }
740 
741             if (b.containsKey(INTENT_ENABLE_WAVCAPTURE)) {
742                 getApp().setCaptureWavsEnabled(b.getBoolean(INTENT_ENABLE_WAVCAPTURE));
743                 mIntentRunning = true;
744             }
745 
746             if (b.containsKey(INTENT_NUM_CAPTURES)) {
747                 getApp().setNumberOfCaptures(b.getInt(INTENT_NUM_CAPTURES));
748                 mIntentRunning = true;
749             }
750 
751             if (b.containsKey(INTENT_WAV_DURATION)) {
752                 getApp().setBufferTestWavePlotDuration(b.getInt(INTENT_WAV_DURATION));
753                 mIntentRunning = true;
754             }
755 
756             if (b.containsKey(INTENT_USB_AUDIO_ROUTE)) {
757                 waitForUsbRoute();
758             }
759 
760             if (mIntentRunning || b.containsKey(INTENT_TEST_TYPE)) {
761                 // run tests with provided or default parameters
762                 refreshState();
763 
764                 // if no test is specified then Latency Test will be run
765                 int testType = b.getInt(INTENT_TEST_TYPE,
766                         Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY);
767                 switch (testType) {
768                     case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
769                         startBufferTest();
770                         break;
771                     case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_CALIBRATION:
772                         doCalibration();
773                         break;
774                     case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
775                     default:
776                         if (b.containsKey(Constant.KEY_CTSINVOCATION)) {
777                             mIsCTSTest = true;
778                             mCtsNumIterations = b.getInt(Constant.KEY_NUMITERATIONS);
779                         }
780                         startLatencyTest();
781                         break;
782                 }
783             }
784 
785         } else {
786             if (mIntentRunning && b != null) {
787                 log("Test already in progress");
788                 showToast("Test already in progress");
789             }
790         }
791     }
792 
793 
794     /** Stop all currently running threads that are related to audio test. */
stopAudioTestThreads()795     private void stopAudioTestThreads() {
796         log("stopping audio threads");
797         if (mAudioThread != null) {
798             try {
799                 mAudioThread.finish();
800                 mAudioThread.join(Constant.JOIN_WAIT_TIME_MS);
801             } catch (InterruptedException e) {
802                 e.printStackTrace();
803             }
804             mAudioThread = null;
805         }
806 
807         if (mNativeAudioThread != null) {
808             try {
809                 mNativeAudioThread.finish();
810                 mNativeAudioThread.join(Constant.JOIN_WAIT_TIME_MS);
811             } catch (InterruptedException e) {
812                 e.printStackTrace();
813             }
814             mNativeAudioThread = null;
815         }
816 
817         stopLoadThreads();
818         System.gc();
819     }
820 
821 
onDestroy()822     public void onDestroy() {
823         stopAudioTestThreads();
824         super.onDestroy();
825         stopService(new Intent(this, AudioTestService.class));
826     }
827 
828 
829     @Override
onResume()830     protected void onResume() {
831         super.onResume();
832         log("on resume called");
833         setTransportButtonsState(NO_TEST_ACTIVE);
834     }
835 
836 
837 //    @Override
838 //    protected void onPause() {
839 //        super.onPause();
840 //        // this means that this activity will not be recreated now, user is leaving it
841 //        // or the activity is otherwise finishing
842 //        if(isFinishing()) {
843 //            FragmentManager fm = getFragmentManager();
844 //            // we will not need this fragment anymore, this may also be a good place to signal
845 //            // to the retained fragment object to perform its own cleanup.
846 //            fm.beginTransaction().remove(mRetainedFragment).commit();
847 //        }
848 //    }
849 
850     @Override
onCreateOptionsMenu(Menu menu)851     public boolean onCreateOptionsMenu(Menu menu) {
852         MenuInflater inflater = getMenuInflater();
853         inflater.inflate(R.menu.tool_bar_menu, menu);
854         return true;
855     }
856 
857     @Override
onOptionsItemSelected(MenuItem item)858     public boolean onOptionsItemSelected(MenuItem item) {
859         // Respond to user selecting action bar buttons
860         switch (item.getItemId()) {
861             case R.id.action_help:
862                 if (!isBusy()) {
863                     // Launch about Activity
864                     Intent aboutIntent = new Intent(this, AboutActivity.class);
865                     startActivity(aboutIntent);
866                 } else {
867                     showToast("Test in progress... please wait");
868                 }
869 
870                 return true;
871 
872             case R.id.action_settings:
873                 if (!isBusy()) {
874                     // Launch settings activity
875                     Intent mySettingsIntent = new Intent(this, SettingsActivity.class);
876                     startActivityForResult(mySettingsIntent, SETTINGS_ACTIVITY_REQUEST);
877                 } else {
878                     showToast("Test in progress... please wait");
879                 }
880                 return true;
881 
882             default:
883                 break;
884         }
885 
886         return super.onOptionsItemSelected(item);
887     }
888 
889 
890     /** Check if the app is busy (running test). */
isBusy()891     public boolean isBusy() {
892         boolean busy = false;
893 
894         if (mAudioThread != null && mAudioThread.mIsRunning) {
895             busy = true;
896         }
897 
898         if (mNativeAudioThread != null && mNativeAudioThread.mIsRunning) {
899             busy = true;
900         }
901 
902         return busy;
903     }
904 
905 
906     /** Create a new audio thread according to the settings. */
restartAudioSystem()907     private void restartAudioSystem() {
908         log("restart audio system...");
909 
910         int sessionId = 0; /* FIXME runtime test for am.generateAudioSessionId() in API 21 */
911 
912         mAudioThreadType = getApp().getAudioThreadType();
913         mSamplingRate = getApp().getSamplingRate();
914         mChannelIndex = getApp().getChannelIndex();
915         mPlayerBufferSizeInBytes = getApp().getPlayerBufferSizeInBytes();
916         mRecorderBufferSizeInBytes = getApp().getRecorderBufferSizeInBytes();
917         mTestStartTimeString = (String) DateFormat.format("MMddkkmmss",
918                 System.currentTimeMillis());
919         mMicSource = getApp().getMicSource();
920         mPerformanceMode = getApp().getPerformanceMode();
921         mIgnoreFirstFrames = getApp().getIgnoreFirstFrames();
922         AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
923         mSoundLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC);
924         mBufferTestDurationInSeconds = getApp().getBufferTestDuration();
925         mBufferTestWavePlotDurationInSeconds = getApp().getBufferTestWavePlotDuration();
926 
927         mCaptureHolder = new CaptureHolder(getApp().getNumStateCaptures(),
928                 getFileNamePrefix(), getApp().isCaptureWavSnippetsEnabled(),
929                 getApp().isCaptureSysTraceEnabled(), getApp().isCaptureBugreportEnabled(),
930                 this, mSamplingRate);
931 
932         log(" current sampling rate: " + mSamplingRate);
933         stopAudioTestThreads();
934 
935         // select java or native audio thread
936         int micSourceMapped;
937 
938         switch (mAudioThreadType) {
939 
940         case Constant.AUDIO_THREAD_TYPE_JAVA:
941             micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_JAVA, mMicSource);
942 
943             int expectedRecorderBufferPeriod = Math.round(
944                     (float) (mRecorderBufferSizeInBytes * Constant.MILLIS_PER_SECOND)
945                             / (Constant.BYTES_PER_FRAME * mSamplingRate));
946             mRecorderBufferPeriod.prepareMemberObjects(
947                     Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds,
948                     expectedRecorderBufferPeriod, mCaptureHolder);
949 
950             int expectedPlayerBufferPeriod = Math.round(
951                     (float) (mPlayerBufferSizeInBytes * Constant.MILLIS_PER_SECOND)
952                             / (Constant.BYTES_PER_FRAME * mSamplingRate));
953             mPlayerBufferPeriod.prepareMemberObjects(
954                     Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND * mBufferTestDurationInSeconds,
955                     expectedPlayerBufferPeriod, mCaptureHolder);
956 
957             mAudioThread = new LoopbackAudioThread(mSamplingRate, mPlayerBufferSizeInBytes,
958                           mRecorderBufferSizeInBytes, micSourceMapped,
959                           /* no performance mode */ mRecorderBufferPeriod,
960                           mPlayerBufferPeriod, mTestType, mBufferTestDurationInSeconds,
961                           mBufferTestWavePlotDurationInSeconds, getApplicationContext(),
962                           mChannelIndex, mCaptureHolder);
963             mAudioThread.setMessageHandler(mMessageHandler);
964             mAudioThread.mSessionId = sessionId;
965             mAudioThread.start();
966             break;
967         case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
968         case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
969             micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_NATIVE_SLES,
970                     mMicSource);
971             int performanceModeMapped = getApp().mapPerformanceMode(mPerformanceMode);
972             // Note: mRecorderBufferSizeInBytes will not actually be used, since recorder buffer
973             // size = player buffer size in native mode
974             mNativeAudioThread = new NativeAudioThread(mAudioThreadType, mSamplingRate,
975                                 mPlayerBufferSizeInBytes, mRecorderBufferSizeInBytes,
976                                 micSourceMapped, performanceModeMapped, mTestType,
977                                 mBufferTestDurationInSeconds, mBufferTestWavePlotDurationInSeconds,
978                                 mIgnoreFirstFrames, mCaptureHolder);
979             mNativeAudioThread.setMessageHandler(mMessageHandler);
980             mNativeAudioThread.mSessionId = sessionId;
981             mNativeAudioThread.start();
982             break;
983         }
984 
985         startLoadThreads();
986 
987         mMessageHandler.post(new Runnable() {
988             @Override
989             public void run() {
990                 refreshState();
991             }
992         });
993     }
994 
995 
996     /** Start all LoadThread. */
startLoadThreads()997     private void startLoadThreads() {
998 
999         if (getApp().getNumberOfLoadThreads() > 0) {
1000 
1001             mLoadThreads = new LoadThread[getApp().getNumberOfLoadThreads()];
1002 
1003             for (int i = 0; i < mLoadThreads.length; i++) {
1004                 mLoadThreads[i] = new LoadThread("Loopback_LoadThread_" + i);
1005                 mLoadThreads[i].start();
1006             }
1007         }
1008     }
1009 
1010 
1011     /** Stop all LoadThread. */
stopLoadThreads()1012     private void stopLoadThreads() {
1013         log("stopping load threads");
1014         if (mLoadThreads != null) {
1015             for (int i = 0; i < mLoadThreads.length; i++) {
1016                 if (mLoadThreads[i] != null) {
1017                     try {
1018                         mLoadThreads[i].requestStop();
1019                         mLoadThreads[i].join(Constant.JOIN_WAIT_TIME_MS);
1020                     } catch (InterruptedException e) {
1021                         e.printStackTrace();
1022                     }
1023                     mLoadThreads[i] = null;
1024                 }
1025             }
1026         }
1027     }
1028 
1029 
resetBufferPeriodRecord(BufferPeriod recorderBufferPeriod, BufferPeriod playerBufferPeriod)1030     private void resetBufferPeriodRecord(BufferPeriod recorderBufferPeriod,
1031                                          BufferPeriod playerBufferPeriod) {
1032         recorderBufferPeriod.resetRecord();
1033         playerBufferPeriod.resetRecord();
1034     }
1035 
canPerformBufferTest()1036     private boolean canPerformBufferTest() {
1037         // Retrieve the thread type from the app, because mAudioThreadType
1038         // only gets populated during the start of the test.
1039         switch (getApp().getAudioThreadType()) {
1040             case Constant.AUDIO_THREAD_TYPE_JAVA:
1041             case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1042                 return true;
1043         }
1044         // Buffer test isn't yet implemented for AAudio.
1045         return false;
1046     }
1047 
setTransportButtonsState(int state)1048     private void setTransportButtonsState(int state) {
1049         Button latencyStart = (Button) findViewById(R.id.buttonStartLatencyTest);
1050         Button bufferStart = (Button) findViewById(R.id.buttonStartBufferTest);
1051         Button calibrationStart = (Button) findViewById(R.id.buttonCalibrateSoundLevel);
1052 
1053         boolean canEnableBufferTest = canPerformBufferTest();
1054 
1055         switch (state) {
1056             case NO_TEST_ACTIVE:
1057                 latencyStart.setEnabled(true);
1058                 bufferStart.setEnabled(canEnableBufferTest);
1059                 calibrationStart.setEnabled(true);
1060                 break;
1061 
1062             case LATENCY_TEST_STARTED:
1063                 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE);
1064                 mTextViewResultSummary.setText("");
1065                 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE);
1066                 latencyStart.setCompoundDrawablesWithIntrinsicBounds(
1067                         R.drawable.ic_stop, 0, 0, 0);
1068                 bufferStart.setEnabled(false);
1069                 calibrationStart.setEnabled(false);
1070                 break;
1071 
1072             case LATENCY_TEST_ENDED:
1073                 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE);
1074                 latencyStart.setCompoundDrawablesWithIntrinsicBounds(
1075                         R.drawable.ic_play_arrow, 0, 0, 0);
1076                 bufferStart.setEnabled(canEnableBufferTest);
1077                 calibrationStart.setEnabled(true);
1078                 break;
1079 
1080             case BUFFER_TEST_STARTED:
1081                 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE);
1082                 mTextViewResultSummary.setText("");
1083                 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE);
1084                 bufferStart.setCompoundDrawablesWithIntrinsicBounds(
1085                         R.drawable.ic_stop, 0, 0, 0);
1086                 latencyStart.setEnabled(false);
1087                 calibrationStart.setEnabled(false);
1088                 break;
1089 
1090             case BUFFER_TEST_ENDED:
1091                 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE);
1092                 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE);
1093                 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE);
1094                 bufferStart.setCompoundDrawablesWithIntrinsicBounds(
1095                         R.drawable.ic_play_arrow, 0, 0, 0);
1096                 latencyStart.setEnabled(true);
1097                 calibrationStart.setEnabled(true);
1098                 break;
1099 
1100             case CALIBRATION_STARTED:
1101                 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE);
1102                 findViewById(R.id.resultSummary).setVisibility(View.INVISIBLE);
1103                 findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE);
1104                 calibrationStart.setCompoundDrawablesWithIntrinsicBounds(
1105                         R.drawable.ic_stop, 0, 0, 0);
1106                 latencyStart.setEnabled(false);
1107                 bufferStart.setEnabled(false);
1108                 calibrationStart.setEnabled(false);
1109                 break;
1110 
1111             case CALIBRATION_ENDED:
1112                 findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE);
1113                 findViewById(R.id.resultSummary).setVisibility(View.VISIBLE);
1114                 findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE);
1115                 calibrationStart.setCompoundDrawablesWithIntrinsicBounds(
1116                         R.drawable.ic_play_arrow, 0, 0, 0);
1117                 latencyStart.setEnabled(true);
1118                 bufferStart.setEnabled(canEnableBufferTest);
1119                 calibrationStart.setEnabled(true);
1120                 break;
1121 
1122             default:
1123                 break;
1124         }
1125     }
1126 
doCalibrationIfEnabled(final Runnable onComplete)1127     private void doCalibrationIfEnabled(final Runnable onComplete) {
1128         if (getApp().isSoundLevelCalibrationEnabled()) {
1129             doCalibration(onComplete);
1130         } else {
1131             if (onComplete != null) {
1132                 onComplete.run();
1133             }
1134         }
1135     }
1136 
doCalibration()1137     private void doCalibration() {
1138         doCalibration(null);
1139     }
1140 
doCalibration(final Runnable onComplete)1141     private void doCalibration(final Runnable onComplete) {
1142         if (isBusy()) {
1143             showToast("Test in progress... please wait");
1144             return;
1145         }
1146 
1147         if (!hasRecordAudioPermission()) {
1148             requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY);
1149             // Returning, otherwise we don't know if user accepted or rejected.
1150             return;
1151         }
1152 
1153         showToast("Calibrating sound level...");
1154         final SoundLevelCalibration calibration =
1155                 new SoundLevelCalibration(getApp().getAudioThreadType(),
1156                         getApp().getSamplingRate(),
1157                         getApp().getPlayerBufferSizeInBytes(),
1158                         getApp().getRecorderBufferSizeInBytes(),
1159                         getApp().getMicSource(), getApp().getPerformanceMode(), this);
1160 
1161         calibration.setChangeListener(new SoundLevelCalibration.SoundLevelChangeListener() {
1162             @Override
1163             void onChange(int newLevel) {
1164                 refreshSoundLevelBar();
1165             }
1166         });
1167 
1168         mCalibrationThread = new Thread(new Runnable() {
1169             @Override
1170             public void run() {
1171                 calibration.calibrate();
1172                 showToast("Calibration complete");
1173                 if (onComplete != null) {
1174                     onComplete.run();
1175                 }
1176             }
1177         });
1178 
1179         mCalibrationThread.start();
1180     }
1181 
1182     /** Start the latency test. */
onButtonLatencyTest(View view)1183     public void onButtonLatencyTest(View view) throws InterruptedException {
1184         if (isBusy()) {
1185             stopTests();
1186             return;
1187         }
1188 
1189         // Ensure we have RECORD_AUDIO permissions
1190         // On Android M (API 23) we must request dangerous permissions each time we use them
1191         if (hasRecordAudioPermission()) {
1192             startLatencyTest();
1193         } else {
1194             requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY);
1195         }
1196     }
1197 
startLatencyTest()1198     private void startLatencyTest() {
1199         if (isBusy()) {
1200             showToast("Test in progress... please wait");
1201             return;
1202         }
1203 
1204         doCalibrationIfEnabled(latencyTestRunnable);
1205     }
1206 
1207     private Runnable latencyTestRunnable = new Runnable() {
1208         @Override
1209         public void run() {
1210             if (isBusy()) {
1211                 showToast("Test in progress... please wait");
1212                 return;
1213             }
1214 
1215             mBarMasterLevel.post(new Runnable() {
1216                 @Override
1217                 public void run() {
1218                     mBarMasterLevel.setEnabled(false);
1219                 }
1220             });
1221             resetBufferPeriodRecord(mRecorderBufferPeriod, mPlayerBufferPeriod);
1222 
1223             mTestType = Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY;
1224             restartAudioSystem();
1225             try {
1226                 Thread.sleep(THREAD_SLEEP_DURATION_MS);
1227             } catch (InterruptedException e) {
1228                 e.printStackTrace();
1229             }
1230 
1231             switch (mAudioThreadType) {
1232                 case Constant.AUDIO_THREAD_TYPE_JAVA:
1233                     if (mAudioThread != null) {
1234                         mAudioThread.runTest();
1235                     }
1236                     break;
1237                 case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1238                 case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1239                     if (mNativeAudioThread != null) {
1240                         mNativeAudioThread.runTest();
1241                     }
1242                     break;
1243                 default:
1244                     break;
1245             }
1246         }
1247     };
1248 
1249 
1250     /** Start the Buffer (Glitch Detection) Test. */
onButtonBufferTest(View view)1251     public void onButtonBufferTest(View view) throws InterruptedException {
1252         if (isBusy()) {
1253             stopTests();
1254             return;
1255         }
1256 
1257         if (hasRecordAudioPermission()) {
1258             startBufferTest();
1259         } else {
1260             requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER);
1261         }
1262     }
1263 
1264 
startBufferTest()1265     private void startBufferTest() {
1266         // In the interactive mode the buffer test button should be disabled
1267         // if the buffer test isn't supported, but the function can also be invoked
1268         // via an intent.
1269         if (!canPerformBufferTest()) {
1270             showToastImportant("Buffer test is not supported with this thread type");
1271             log("Buffer test is not supported with this thread type");
1272             return;
1273         }
1274 
1275         if (!isBusy()) {
1276             mBarMasterLevel.setEnabled(false);
1277             resetBufferPeriodRecord(mRecorderBufferPeriod, mPlayerBufferPeriod);
1278             mTestType = Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD;
1279             restartAudioSystem();   // in this function a audio thread is created
1280             try {
1281                 Thread.sleep(THREAD_SLEEP_DURATION_MS);
1282             } catch (InterruptedException e) {
1283                 e.printStackTrace();
1284             }
1285 
1286             switch (mAudioThreadType) {
1287             case Constant.AUDIO_THREAD_TYPE_JAVA:
1288                 if (mAudioThread != null) {
1289                     mAudioThread.runBufferTest();
1290                 }
1291                 break;
1292             case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1293             case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1294                 if (mNativeAudioThread != null) {
1295                     mNativeAudioThread.runBufferTest();
1296                 }
1297                 break;
1298             default:
1299                 break;
1300             }
1301         } else {
1302             int duration = 0;
1303             switch (mAudioThreadType) {
1304             case Constant.AUDIO_THREAD_TYPE_JAVA:
1305                 duration = mAudioThread.getDurationInSeconds();
1306                 break;
1307             case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1308             case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1309                 duration = mNativeAudioThread.getDurationInSeconds();
1310                 break;
1311             default:
1312                 break;
1313             }
1314             showToastImportant("Long-run Test in progress, in total should take " +
1315                     Integer.toString(duration) + "s, please wait");
1316         }
1317     }
1318 
1319 
1320     /** Stop the ongoing test. */
stopTests()1321     public void stopTests() throws InterruptedException {
1322         if (mAudioThread != null) {
1323             mAudioThread.requestStopTest();
1324         }
1325 
1326         if (mNativeAudioThread != null) {
1327             mNativeAudioThread.requestStopTest();
1328         }
1329     }
1330 
onButtonCalibrateSoundLevel(final View view)1331     public void onButtonCalibrateSoundLevel(final View view) {
1332         Message m = Message.obtain();
1333         m.what = CALIBRATION_STARTED;
1334         mMessageHandler.sendMessage(m);
1335         Runnable onComplete = new Runnable() {
1336             @Override
1337             public void run() {
1338                 Message m = Message.obtain();
1339                 m.what = CALIBRATION_ENDED;
1340                 mMessageHandler.sendMessage(m);
1341             }
1342         };
1343         doCalibration(onComplete);
1344     }
1345 
1346     /***
1347      * Show dialog to choose to save files with filename dialog or not
1348      */
onButtonSave(View view)1349     public void onButtonSave(View view) {
1350         if (!isBusy()) {
1351             DialogFragment newFragment = new SaveFilesDialogFragment();
1352             newFragment.show(getFragmentManager(), "saveFiles");
1353         } else {
1354             showToast("Test in progress... please wait");
1355         }
1356     }
1357 
1358     /**
1359      * Save five files: one .png file for a screenshot on the main activity, one .wav file for
1360      * the plot displayed on the main activity, one .txt file for storing various test results, one
1361      * .txt file for storing recorder buffer period data, and one .txt file for storing player
1362      * buffer period data.
1363      */
SaveFilesWithDialog()1364     private void SaveFilesWithDialog() {
1365 
1366         String fileName = "loopback_" + mTestStartTimeString;
1367 
1368         //Launch filename choosing activities if available, otherwise save without prompting
1369         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
1370             launchFileNameChoosingActivity("text/plain", fileName, ".txt", SAVE_TO_TXT_REQUEST);
1371             launchFileNameChoosingActivity("image/png", fileName, ".png", SAVE_TO_PNG_REQUEST);
1372             launchFileNameChoosingActivity("audio/wav", fileName, ".wav", SAVE_TO_WAVE_REQUEST);
1373             launchFileNameChoosingActivity("text/plain", fileName, "_recorderBufferPeriod.txt",
1374                     SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST);
1375             launchFileNameChoosingActivity("text/plain", fileName, "_recorderBufferPeriodTimes.txt",
1376                     SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST);
1377             launchFileNameChoosingActivity("image/png", fileName, "_recorderBufferPeriod.png",
1378                     SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST);
1379             launchFileNameChoosingActivity("text/plain", fileName, "_playerBufferPeriod.txt",
1380                     SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST);
1381             launchFileNameChoosingActivity("text/plain", fileName, "_playerBufferPeriodTimes.txt",
1382                     SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST);
1383             launchFileNameChoosingActivity("image/png", fileName, "_playerBufferPeriod.png",
1384                     SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST);
1385 
1386             if (mGlitchesData != null) {
1387                 launchFileNameChoosingActivity("text/plain", fileName, "_glitchMillis.txt",
1388                         SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST);
1389                 launchFileNameChoosingActivity("image/png", fileName, "_heatMap.png",
1390                         SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST);
1391             }
1392         } else {
1393             saveAllTo(fileName);
1394         }
1395     }
1396 
1397     /**
1398      * Launches an activity for choosing the filename of the file to be saved
1399      */
launchFileNameChoosingActivity(String type, String fileName, String suffix, int RequestCode)1400     public void launchFileNameChoosingActivity(String type, String fileName, String suffix,
1401                                                int RequestCode) {
1402         Intent FilenameIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
1403         FilenameIntent.addCategory(Intent.CATEGORY_OPENABLE);
1404         FilenameIntent.setType(type);
1405         FilenameIntent.putExtra(Intent.EXTRA_TITLE, fileName + suffix);
1406         startActivityForResult(FilenameIntent, RequestCode);
1407     }
1408 
getFileNamePrefix()1409     private String getFileNamePrefix() {
1410         if (mIntentFileName != null && !mIntentFileName.isEmpty()) {
1411             return mIntentFileName;
1412         } else {
1413             return "loopback_" + mTestStartTimeString;
1414         }
1415     }
1416 
1417     /** See the documentation on onButtonSave() */
saveAllTo(String fileName)1418     public void saveAllTo(String fileName) {
1419 
1420         if (!hasWriteFilePermission()) {
1421             requestWriteFilePermission(PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS);
1422             return;
1423         }
1424 
1425         showToast("Saving files to: " + fileName + ".(wav,png,txt)");
1426 
1427         //save to a given uri... local file?
1428         saveToWaveFile(Uri.parse(FILE_SAVE_PATH + fileName + ".wav"));
1429 
1430         saveScreenShot(Uri.parse(FILE_SAVE_PATH + fileName + ".png"));
1431 
1432         saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + ".txt"), getReport().toString());
1433 
1434         int[] bufferPeriodArray = null;
1435         int maxBufferPeriod = Constant.UNKNOWN;
1436         switch (mAudioThreadType) {
1437         case Constant.AUDIO_THREAD_TYPE_JAVA:
1438             bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray();
1439             maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod();
1440             break;
1441         case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1442         case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1443             bufferPeriodArray = mNativeRecorderBufferPeriodArray;
1444             maxBufferPeriod = mNativeRecorderMaxBufferPeriod;
1445             break;
1446         default:
1447             break;
1448         }
1449         saveBufferPeriod(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriod.txt"),
1450                 bufferPeriodArray, maxBufferPeriod);
1451         saveHistogram(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriod.png"),
1452                 bufferPeriodArray, maxBufferPeriod);
1453         saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + "_recorderBufferPeriodTimes.txt"),
1454                 mRecorderCallbackTimes.toString());
1455 
1456         bufferPeriodArray = null;
1457         maxBufferPeriod = Constant.UNKNOWN;
1458         switch (mAudioThreadType) {
1459         case Constant.AUDIO_THREAD_TYPE_JAVA:
1460             bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray();
1461             maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod();
1462             break;
1463         case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1464         case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1465             bufferPeriodArray = mNativePlayerBufferPeriodArray;
1466             maxBufferPeriod = mNativePlayerMaxBufferPeriod;
1467             break;
1468         default:
1469             break;
1470         }
1471         saveBufferPeriod(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriod.txt")
1472                 , bufferPeriodArray, maxBufferPeriod);
1473         saveHistogram(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriod.png"),
1474                 bufferPeriodArray, maxBufferPeriod);
1475         saveTextToFile(Uri.parse(FILE_SAVE_PATH + fileName + "_playerBufferPeriodTimes.txt"),
1476                 mPlayerCallbackTimes.toString());
1477 
1478         if (mGlitchesData != null) {
1479             saveGlitchOccurrences(Uri.parse(FILE_SAVE_PATH + fileName + "_glitchMillis.txt"),
1480                     mGlitchesData);
1481             saveHeatMap(Uri.parse(FILE_SAVE_PATH + fileName + "_heatMap.png"),
1482                     mRecorderCallbackTimes, mPlayerCallbackTimes,
1483                     GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize,
1484                             mFFTOverlapSamples, mGlitchesData, mSamplingRate),
1485                     mGlitchingIntervalTooLong, mBufferTestElapsedSeconds, fileName);
1486         }
1487 
1488     }
1489 
1490 
1491     @Override
onActivityResult(int requestCode, int resultCode, Intent resultData)1492     public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
1493         log("ActivityResult request: " + requestCode + "  result:" + resultCode);
1494 
1495         if (resultCode == Activity.RESULT_OK) {
1496             switch (requestCode) {
1497 
1498             case SAVE_TO_WAVE_REQUEST:
1499                 log("got SAVE TO WAV intent back!");
1500                 if (resultData != null) {
1501                     saveToWaveFile(resultData.getData());
1502                 }
1503                 break;
1504 
1505             case SAVE_TO_PNG_REQUEST:
1506                 log("got SAVE TO PNG intent back!");
1507                 if (resultData != null) {
1508                     saveScreenShot(resultData.getData());
1509                 }
1510                 break;
1511 
1512             case SAVE_TO_TXT_REQUEST:
1513                 if (resultData != null) {
1514                     saveTextToFile(resultData.getData(), getReport().toString());
1515                 }
1516                 break;
1517 
1518             case SAVE_RECORDER_BUFFER_PERIOD_TO_TXT_REQUEST:
1519                 if (resultData != null) {
1520                     int[] bufferPeriodArray = null;
1521                     int maxBufferPeriod = Constant.UNKNOWN;
1522                     switch (mAudioThreadType) {
1523                     case Constant.AUDIO_THREAD_TYPE_JAVA:
1524                         bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray();
1525                         maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod();
1526                         break;
1527                     case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1528                     case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1529                         bufferPeriodArray = mNativeRecorderBufferPeriodArray;
1530                         maxBufferPeriod = mNativeRecorderMaxBufferPeriod;
1531                         break;
1532                     }
1533                     saveBufferPeriod(resultData.getData(), bufferPeriodArray, maxBufferPeriod);
1534                 }
1535                 break;
1536 
1537             case SAVE_PLAYER_BUFFER_PERIOD_TO_TXT_REQUEST:
1538                 if (resultData != null) {
1539                     int[] bufferPeriodArray = null;
1540                     int maxBufferPeriod = Constant.UNKNOWN;
1541                     switch (mAudioThreadType) {
1542                     case Constant.AUDIO_THREAD_TYPE_JAVA:
1543                         bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray();
1544                         maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod();
1545                         break;
1546                     case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1547                     case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1548                         bufferPeriodArray = mNativePlayerBufferPeriodArray;
1549                         maxBufferPeriod = mNativePlayerMaxBufferPeriod;
1550                         break;
1551                     }
1552                     saveBufferPeriod(resultData.getData(), bufferPeriodArray, maxBufferPeriod);
1553                 }
1554                 break;
1555 
1556             case SAVE_RECORDER_BUFFER_PERIOD_TO_PNG_REQUEST:
1557                 if (resultData != null) {
1558                     int[] bufferPeriodArray = null;
1559                     int maxBufferPeriod = Constant.UNKNOWN;
1560                     switch (mAudioThreadType) {
1561                         case Constant.AUDIO_THREAD_TYPE_JAVA:
1562                             bufferPeriodArray = mRecorderBufferPeriod.getBufferPeriodArray();
1563                             maxBufferPeriod = mRecorderBufferPeriod.getMaxBufferPeriod();
1564                             break;
1565                         case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1566                         case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1567                             bufferPeriodArray = mNativeRecorderBufferPeriodArray;
1568                             maxBufferPeriod = mNativeRecorderMaxBufferPeriod;
1569                             break;
1570                     }
1571                     saveHistogram(resultData.getData(), bufferPeriodArray, maxBufferPeriod);
1572                 }
1573                 break;
1574 
1575             case SAVE_PLAYER_BUFFER_PERIOD_TO_PNG_REQUEST:
1576                 if (resultData != null) {
1577                     int[] bufferPeriodArray = null;
1578                     int maxBufferPeriod = Constant.UNKNOWN;
1579                     switch (mAudioThreadType) {
1580                         case Constant.AUDIO_THREAD_TYPE_JAVA:
1581                             bufferPeriodArray = mPlayerBufferPeriod.getBufferPeriodArray();
1582                             maxBufferPeriod = mPlayerBufferPeriod.getMaxBufferPeriod();
1583                             break;
1584                         case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1585                         case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1586                             bufferPeriodArray = mNativePlayerBufferPeriodArray;
1587                             maxBufferPeriod = mNativePlayerMaxBufferPeriod;
1588                             break;
1589                     }
1590                     saveHistogram(resultData.getData(), bufferPeriodArray, maxBufferPeriod);
1591                 }
1592                 break;
1593 
1594             case SAVE_PLAYER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST:
1595                 if (resultData != null) {
1596                     saveTextToFile(resultData.getData(),
1597                             mPlayerCallbackTimes.toString());
1598                 }
1599                 break;
1600 
1601             case SAVE_RECORDER_BUFFER_PERIOD_TIMES_TO_TXT_REQUEST:
1602                 if (resultData != null) {
1603                     saveTextToFile(resultData.getData(),
1604                             mRecorderCallbackTimes.toString());
1605                 }
1606                 break;
1607 
1608             case SAVE_GLITCH_OCCURRENCES_TO_TEXT_REQUEST:
1609                 if (resultData != null) {
1610                     saveGlitchOccurrences(resultData.getData(), mGlitchesData);
1611                 }
1612                 break;
1613 
1614             case SAVE_GLITCH_AND_CALLBACK_HEATMAP_REQUEST:
1615                 if (resultData != null && mGlitchesData != null && mRecorderCallbackTimes != null
1616                         && mPlayerCallbackTimes != null) {
1617                     saveHeatMap(resultData.getData(), mRecorderCallbackTimes, mPlayerCallbackTimes,
1618                             GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize,
1619                                     mFFTOverlapSamples, mGlitchesData, mSamplingRate),
1620                             mGlitchingIntervalTooLong, mBufferTestElapsedSeconds,
1621                             resultData.getData().toString());
1622                 }
1623 
1624             case SETTINGS_ACTIVITY_REQUEST:
1625                 log("return from new settings!");
1626                 break;
1627 
1628             default:
1629                 break;
1630 
1631             }
1632         }
1633     }
1634 
1635 
1636     /**
1637      * Refresh the sound level bar on the main activity to reflect the current sound level
1638      * of the system.
1639      */
refreshSoundLevelBar()1640     private void refreshSoundLevelBar() {
1641         mBarMasterLevel.setEnabled(true);
1642         AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
1643         int currentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
1644         mBarMasterLevel.setProgress(currentVolume);
1645 
1646         mTextViewCurrentLevel.setText(String.format("Current Sound Level: %d/%d", currentVolume,
1647                 mBarMasterLevel.getMax()));
1648     }
1649 
1650 
1651     /** Reset all results gathered from previous round of test (if any). */
resetResults()1652     private void resetResults() {
1653         mCorrelation.invalidate();
1654         mNativeRecorderBufferPeriodArray = null;
1655         mNativePlayerBufferPeriodArray = null;
1656         mPlayerCallbackTimes = null;
1657         mRecorderCallbackTimes = null;
1658         mGlitchesData = null;
1659         mRetainedFragment.setWaveData(null);
1660     }
1661 
1662 
1663     /** Get the file path from uri. Doesn't work for all devices. */
getPath(Uri uri)1664     private String getPath(Uri uri) {
1665         String[] projection = {MediaStore.Images.Media.DATA};
1666         Cursor cursor1 = getContentResolver().query(uri, projection, null, null, null);
1667         if (cursor1 == null) {
1668             return uri.getPath();
1669         }
1670 
1671         int ColumnIndex = cursor1.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
1672         cursor1.moveToFirst();
1673         String path = cursor1.getString(ColumnIndex);
1674         cursor1.close();
1675         return path;
1676     }
1677 
1678 
1679     /** Zoom out the plot to its full size. */
onButtonZoomOutFull(View view)1680     public void onButtonZoomOutFull(View view) {
1681         double fullZoomOut = mWavePlotView.getMaxZoomOut();
1682         mWavePlotView.setZoom(fullZoomOut);
1683         mWavePlotView.refreshGraph();
1684     }
1685 
1686 
1687     /** Zoom out the plot. */
onButtonZoomOut(View view)1688     public void onButtonZoomOut(View view) {
1689         double zoom = mWavePlotView.getZoom();
1690         zoom = 2.0 * zoom;
1691         mWavePlotView.setZoom(zoom);
1692         mWavePlotView.refreshGraph();
1693     }
1694 
1695 
1696     /** Zoom in the plot. */
onButtonZoomIn(View view)1697     public void onButtonZoomIn(View view) {
1698         double zoom = mWavePlotView.getZoom();
1699         zoom = zoom / 2.0;
1700         mWavePlotView.setZoom(zoom);
1701         mWavePlotView.refreshGraph();
1702     }
1703 
1704 
1705     /** Go to RecorderBufferPeriodActivity */
onButtonRecorderBufferPeriod(View view)1706     public void onButtonRecorderBufferPeriod(View view) {
1707         if (!isBusy()) {
1708             Intent RecorderBufferPeriodIntent = new Intent(this,
1709                                                 RecorderBufferPeriodActivity.class);
1710             int recorderBufferSizeInFrames = mRecorderBufferSizeInBytes / Constant.BYTES_PER_FRAME;
1711             log("recorderBufferSizeInFrames:" + recorderBufferSizeInFrames);
1712 
1713             switch (mAudioThreadType) {
1714             case Constant.AUDIO_THREAD_TYPE_JAVA:
1715                 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodArray",
1716                         mRecorderBufferPeriod.getBufferPeriodArray());
1717                 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodMax",
1718                         mRecorderBufferPeriod.getMaxBufferPeriod());
1719                 break;
1720             case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1721             case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1722                 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodArray",
1723                         mNativeRecorderBufferPeriodArray);
1724                 RecorderBufferPeriodIntent.putExtra("recorderBufferPeriodMax",
1725                         mNativeRecorderMaxBufferPeriod);
1726                 break;
1727             }
1728 
1729             RecorderBufferPeriodIntent.putExtra("recorderBufferSize", recorderBufferSizeInFrames);
1730             RecorderBufferPeriodIntent.putExtra("samplingRate", mSamplingRate);
1731             startActivity(RecorderBufferPeriodIntent);
1732         } else
1733             showToast("Test in progress... please wait");
1734     }
1735 
1736 
1737     /** Go to PlayerBufferPeriodActivity */
onButtonPlayerBufferPeriod(View view)1738     public void onButtonPlayerBufferPeriod(View view) {
1739         if (!isBusy()) {
1740             Intent PlayerBufferPeriodIntent = new Intent(this, PlayerBufferPeriodActivity.class);
1741             int playerBufferSizeInFrames = mPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME;
1742 
1743             switch (mAudioThreadType) {
1744             case Constant.AUDIO_THREAD_TYPE_JAVA:
1745                 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodArray",
1746                         mPlayerBufferPeriod.getBufferPeriodArray());
1747                 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodMax",
1748                         mPlayerBufferPeriod.getMaxBufferPeriod());
1749                 break;
1750             case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1751             case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1752                 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodArray",
1753                         mNativePlayerBufferPeriodArray);
1754                 PlayerBufferPeriodIntent.putExtra("playerBufferPeriodMax",
1755                         mNativePlayerMaxBufferPeriod);
1756                 break;
1757             }
1758 
1759             PlayerBufferPeriodIntent.putExtra("playerBufferSize", playerBufferSizeInFrames);
1760             PlayerBufferPeriodIntent.putExtra("samplingRate", mSamplingRate);
1761             startActivity(PlayerBufferPeriodIntent);
1762         } else
1763             showToast("Test in progress... please wait");
1764     }
1765 
1766 
1767     /** Display pop up window of recorded glitches */
onButtonGlitches(View view)1768     public void onButtonGlitches(View view) {
1769         if (!isBusy()) {
1770             if (mGlitchesData != null) {
1771                 // Create a PopUpWindow with scrollable TextView
1772                 View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null);
1773                 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT,
1774                         ViewGroup.LayoutParams.MATCH_PARENT, true);
1775 
1776                 // Generate report of glitch intervals and set pop up window text
1777                 TextView GlitchText =
1778                         (TextView) popUp.getContentView().findViewById(R.id.ReportInfo);
1779                 GlitchText.setText(GlitchesStringBuilder.getGlitchString(mFFTSamplingSize,
1780                         mFFTOverlapSamples, mGlitchesData, mSamplingRate,
1781                         mGlitchingIntervalTooLong, estimateNumberOfGlitches(mGlitchesData)));
1782 
1783                 // display pop up window, dismissible with back button
1784                 popUp.showAtLocation((View) findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0);
1785             } else {
1786                 showToastImportant("Please run the buffer test to get data");
1787             }
1788 
1789         } else {
1790             showToast("Test in progress... please wait");
1791         }
1792     }
1793 
1794     /** Display pop up window of recorded metrics and system information */
onButtonReport(View view)1795     public void onButtonReport(View view) {
1796         if (!isBusy()) {
1797             if ((mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD
1798                     && mGlitchesData != null)
1799                     || (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY
1800                     && mCorrelation.isValid())) {
1801                 // Create a PopUpWindow with scrollable TextView
1802                 View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null);
1803                 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT,
1804                         ViewGroup.LayoutParams.MATCH_PARENT, true);
1805 
1806                 // Generate report of glitch intervals and set pop up window text
1807                 TextView reportText =
1808                         (TextView) popUp.getContentView().findViewById(R.id.ReportInfo);
1809                 reportText.setText(getReport().toString());
1810 
1811                 // display pop up window, dismissible with back button
1812                 popUp.showAtLocation((View) findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0);
1813             } else {
1814                 showToastImportant("Please run the tests to get data");
1815             }
1816 
1817         } else {
1818             showToast("Test in progress... please wait");
1819         }
1820     }
1821 
1822     /** Display pop up window of recorded metrics and system information */
onButtonHeatMap(View view)1823     public void onButtonHeatMap(View view) {
1824         if (!isBusy()) {
1825             if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD
1826                     && mGlitchesData != null && mRecorderCallbackTimes != null
1827                     && mRecorderCallbackTimes != null) {
1828 
1829                 // Create a PopUpWindow with heatMap custom view
1830                 View puLayout = this.getLayoutInflater().inflate(R.layout.heatmap_window, null);
1831                 PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT,
1832                         ViewGroup.LayoutParams.MATCH_PARENT, true);
1833 
1834                 ((LinearLayout) popUp.getContentView()).addView(
1835                         new GlitchAndCallbackHeatMapView(this, mRecorderCallbackTimes,
1836                                 mPlayerCallbackTimes,
1837                                 GlitchesStringBuilder.getGlitchMilliseconds(mFFTSamplingSize,
1838                                         mFFTOverlapSamples, mGlitchesData, mSamplingRate),
1839                                 mGlitchingIntervalTooLong, mBufferTestElapsedSeconds,
1840                                 getResources().getString(R.string.heatTitle)));
1841 
1842                 popUp.showAtLocation((View) findViewById(R.id.linearLayoutMain), Gravity.TOP, 0, 0);
1843 
1844             } else {
1845                 showToastImportant("Please run the tests to get data");
1846             }
1847 
1848         } else {
1849             showToast("Test in progress... please wait");
1850         }
1851     }
1852 
1853     /** Redraw the plot according to mWaveData */
refreshPlots()1854     private void refreshPlots() {
1855         mWavePlotView.setData(mRetainedFragment.getWaveData(), mSamplingRate);
1856         mWavePlotView.redraw();
1857     }
1858 
audioThreadTypeToString(int audioThreadType)1859     static String audioThreadTypeToString(int audioThreadType) {
1860         switch (audioThreadType) {
1861             case Constant.AUDIO_THREAD_TYPE_JAVA: return "JAVA";
1862             case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES: return "NATIVE (SLES)";
1863             case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO: return "NATIVE (AAUDIO)";
1864         }
1865         return "DEFAULT";
1866     }
1867 
1868     /** Refresh the text on the main activity that shows the app states and audio settings. */
refreshState()1869     private void refreshState() {
1870         log("refreshState!");
1871         refreshSoundLevelBar();
1872 
1873         // get info
1874         int playerFrames = mPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME;
1875         int recorderFrames = mRecorderBufferSizeInBytes / Constant.BYTES_PER_FRAME;
1876         StringBuilder s = new StringBuilder(200);
1877 
1878         s.append("Settings from most recent run (at ");
1879         s.append(mTestStartTimeString);
1880         s.append("):\n");
1881 
1882         s.append("SR: ").append(mSamplingRate).append(" Hz");
1883         s.append(" ChannelIndex: ").append(mChannelIndex < 0 ? "MONO" : mChannelIndex);
1884         switch (mAudioThreadType) {
1885         case Constant.AUDIO_THREAD_TYPE_JAVA:
1886             s.append(" Play Frames: " ).append(playerFrames);
1887             s.append(" Record Frames: ").append(recorderFrames);
1888             break;
1889         case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
1890         case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
1891             s.append(" Frames: ").append(playerFrames);
1892             break;
1893         }
1894         s.append(" Audio: ").append(audioThreadTypeToString(mAudioThreadType));
1895 
1896         // mic source
1897         String micSourceName = getApp().getMicSourceString(mMicSource);
1898         if (micSourceName != null) {
1899             s.append(" Mic: ").append(micSourceName);
1900         }
1901 
1902         // performance mode
1903         String performanceModeName = getApp().getPerformanceModeString(mPerformanceMode);
1904         if (performanceModeName != null) {
1905             s.append(" Performance Mode: ").append(performanceModeName);
1906         }
1907 
1908         // sound level at start of test
1909         s.append(" Sound Level: ").append(mSoundLevel).append("/").append(mBarMasterLevel.getMax());
1910 
1911         // load threads
1912         s.append(" Simulated Load Threads: ").append(getApp().getNumberOfLoadThreads());
1913 
1914         // Show short summary of results, round trip latency or number of glitches
1915         if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY) {
1916             if (mIgnoreFirstFrames > 0) {
1917                 s.append(" First Frames Ignored: ").append(mIgnoreFirstFrames);
1918             }
1919             if (mCorrelation.isValid()) {
1920                 mTextViewResultSummary.setText(String.format("Latency: %.2f ms Confidence: %.2f" +
1921                                 " Average = %.4f RMS = %.4f",
1922                         mCorrelation.mEstimatedLatencyMs, mCorrelation.mEstimatedLatencyConfidence,
1923                         mCorrelation.mAverage, mCorrelation.mRms));
1924             }
1925         } else if (mTestType == Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD &&
1926                 mGlitchesData != null) {
1927             // show buffer test duration
1928             s.append("\nBuffer Test Duration: ").append(mBufferTestDurationInSeconds).append(" s");
1929 
1930             // show buffer test wave plot duration
1931             s.append("   Buffer Test Wave Plot Duration: last ");
1932             s.append(mBufferTestWavePlotDurationInSeconds);
1933             s.append(" s");
1934 
1935             mTextViewResultSummary.setText(getResources().getString(R.string.numGlitches) + " " +
1936                     estimateNumberOfGlitches(mGlitchesData));
1937         } else {
1938             mTextViewResultSummary.setText("");
1939         }
1940 
1941         String info = getApp().getSystemInfo();
1942         s.append(" ").append(info);
1943 
1944         mTextInfo.setText(s.toString());
1945     }
1946 
1947 
log(String msg)1948     private static void log(String msg) {
1949         Log.v(TAG, msg);
1950     }
1951 
showToast(final String msg)1952     public void showToast(final String msg) {
1953         /* doShowToast(msg, false); */
1954     }
1955 
showToastImportant(final String msg)1956     public void showToastImportant(final String msg) {
1957         /* doShowToast(msg, true); */
1958     }
1959 
doShowToast(final String msg, boolean isImportant)1960     private void doShowToast(final String msg, boolean isImportant) {
1961         // If launched from an intent, do not show unimportant toasts.
1962         // Intents are typically used by scripts, which run actions at high
1963         // rate. On some versions of Android this causes the toasts to
1964         // queue up, and they continue displaying long after the app has
1965         // finished the actions.
1966         if (mIntentRunning && !isImportant) {
1967             return;
1968         }
1969         // Make sure UI manipulations are only done on the UI thread
1970         runOnUiThread(new Runnable() {
1971             public void run() {
1972                 Toast toast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT);
1973                 toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 10, 10);
1974                 toast.show();
1975             }
1976         });
1977     }
1978 
1979 
1980     /** Get the application that runs this activity. Wrapper for getApplication(). */
getApp()1981     private LoopbackApplication getApp() {
1982         return (LoopbackApplication) this.getApplication();
1983     }
1984 
1985 
1986     /** Save a .wav file of the wave plot on the main activity. */
saveToWaveFile(Uri uri)1987     private void saveToWaveFile(Uri uri) {
1988         double[] waveData = mRetainedFragment.getWaveData();
1989         if (waveData != null && waveData.length > 0) {
1990             AudioFileOutput audioFileOutput = new AudioFileOutput(getApplicationContext(), uri,
1991                                                                   mSamplingRate);
1992             boolean status = audioFileOutput.writeData(waveData);
1993             if (status) {
1994                 String wavFileAbsolutePath = getPath(uri);
1995                 // for some devices getPath fails
1996                 if (wavFileAbsolutePath != null) {
1997                     File file = new File(wavFileAbsolutePath);
1998                     wavFileAbsolutePath = file.getAbsolutePath();
1999                 } else {
2000                     wavFileAbsolutePath = "";
2001                 }
2002                 showToast("Finished exporting wave File " + wavFileAbsolutePath);
2003             } else {
2004                 showToastImportant("Something failed saving wave file");
2005             }
2006 
2007         }
2008     }
2009 
2010 
2011     /** Save a screenshot of the main activity. */
saveScreenShot(Uri uri)2012     private void saveScreenShot(Uri uri) {
2013         ParcelFileDescriptor parcelFileDescriptor = null;
2014         FileOutputStream outputStream;
2015         try {
2016             parcelFileDescriptor = getApplicationContext().getContentResolver().
2017                                    openFileDescriptor(uri, "w");
2018 
2019             FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
2020             outputStream = new FileOutputStream(fileDescriptor);
2021 
2022             log("Done creating output stream");
2023 
2024             LinearLayout LL = (LinearLayout) findViewById(R.id.linearLayoutMain);
2025 
2026             View v = LL.getRootView();
2027             v.setDrawingCacheEnabled(true);
2028             Bitmap b = v.getDrawingCache();
2029 
2030             //save
2031             b.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
2032             parcelFileDescriptor.close();
2033             v.setDrawingCacheEnabled(false);
2034         } catch (Exception e) {
2035             log("Failed to open png file " + e);
2036         } finally {
2037             try {
2038                 if (parcelFileDescriptor != null) {
2039                     parcelFileDescriptor.close();
2040                 }
2041             } catch (Exception e) {
2042                 e.printStackTrace();
2043                 log("Error closing ParcelFile Descriptor");
2044             }
2045         }
2046     }
2047 
saveHistogram(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod)2048     private void saveHistogram(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod) {
2049         // Create and histogram view bitmap
2050         HistogramView recordHisto = new HistogramView(this,null);
2051         recordHisto.setBufferPeriodArray(bufferPeriodArray);
2052         recordHisto.setMaxBufferPeriod(maxBufferPeriod);
2053 
2054         // Draw histogram on bitmap canvas
2055         Bitmap histoBmp = Bitmap.createBitmap(HISTOGRAM_EXPORT_WIDTH,
2056                 HISTOGRAM_EXPORT_HEIGHT, Bitmap.Config.ARGB_8888); // creates a MUTABLE bitmap
2057         recordHisto.fillCanvas(new Canvas(histoBmp), histoBmp.getWidth(), histoBmp.getHeight());
2058 
2059         saveImage(uri, histoBmp);
2060     }
2061 
saveHeatMap(Uri uri, BufferCallbackTimes recorderCallbackTimes, BufferCallbackTimes playerCallbackTimes, int[] glitchMilliseconds, boolean glitchesExceeded, int duration, String title)2062     private void saveHeatMap(Uri uri, BufferCallbackTimes recorderCallbackTimes,
2063                              BufferCallbackTimes playerCallbackTimes, int[] glitchMilliseconds,
2064                              boolean glitchesExceeded, int duration, String title) {
2065         Bitmap heatBmp = Bitmap.createBitmap(HEATMAP_DRAW_WIDTH, HEATMAP_DRAW_HEIGHT,
2066                 Bitmap.Config.ARGB_8888);
2067         GlitchAndCallbackHeatMapView.fillCanvas(new Canvas(heatBmp), recorderCallbackTimes,
2068                 playerCallbackTimes, glitchMilliseconds, glitchesExceeded, duration,
2069                 title);
2070         saveImage(uri, Bitmap.createScaledBitmap(heatBmp,
2071                 HEATMAP_DRAW_WIDTH / HEATMAP_EXPORT_DIVISOR,
2072                 HEATMAP_DRAW_HEIGHT / HEATMAP_EXPORT_DIVISOR, false));
2073     }
2074 
2075     /** Save an image to file. */
saveImage(Uri uri, Bitmap bmp)2076     private void saveImage(Uri uri, Bitmap bmp) {
2077         ParcelFileDescriptor parcelFileDescriptor = null;
2078         FileOutputStream outputStream;
2079         try {
2080             parcelFileDescriptor = getApplicationContext().getContentResolver().
2081                     openFileDescriptor(uri, "w");
2082 
2083             FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
2084             outputStream = new FileOutputStream(fileDescriptor);
2085 
2086             log("Done creating output stream");
2087 
2088             // Save compressed bitmap to file
2089             bmp.compress(Bitmap.CompressFormat.PNG, EXPORTED_IMAGE_QUALITY, outputStream);
2090             parcelFileDescriptor.close();
2091         } catch (Exception e) {
2092             log("Failed to open png file " + e);
2093         } finally {
2094             try {
2095                 if (parcelFileDescriptor != null) {
2096                     parcelFileDescriptor.close();
2097                 }
2098             } catch (Exception e) {
2099                 e.printStackTrace();
2100                 log("Error closing ParcelFile Descriptor");
2101             }
2102         }
2103     }
2104 
2105 
2106     /**
2107      * Save a .txt file of the given buffer period's data.
2108      * First column is time, second column is count.
2109      */
saveBufferPeriod(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod)2110     private void saveBufferPeriod(Uri uri, int[] bufferPeriodArray, int maxBufferPeriod) {
2111         ParcelFileDescriptor parcelFileDescriptor = null;
2112         FileOutputStream outputStream;
2113         if (bufferPeriodArray != null) {
2114             try {
2115                 parcelFileDescriptor = getApplicationContext().getContentResolver().
2116                         openFileDescriptor(uri, "w");
2117 
2118                 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
2119                 outputStream = new FileOutputStream(fileDescriptor);
2120                 log("Done creating output stream for saving buffer period");
2121 
2122                 int usefulDataRange = Math.min(maxBufferPeriod + 1, bufferPeriodArray.length);
2123                 int[] usefulBufferData = Arrays.copyOfRange(bufferPeriodArray, 0, usefulDataRange);
2124 
2125                 String endline = "\n";
2126                 String delimiter = ",";
2127                 StringBuilder sb = new StringBuilder();
2128                 for (int i = 0; i < usefulBufferData.length; i++) {
2129                     sb.append(i + delimiter + usefulBufferData[i] + endline);
2130                 }
2131 
2132                 outputStream.write(sb.toString().getBytes());
2133 
2134             } catch (Exception e) {
2135                 log("Failed to open text file " + e);
2136             } finally {
2137                 try {
2138                     if (parcelFileDescriptor != null) {
2139                         parcelFileDescriptor.close();
2140                     }
2141                 } catch (Exception e) {
2142                     e.printStackTrace();
2143                     log("Error closing ParcelFile Descriptor");
2144                 }
2145             }
2146         }
2147 
2148     }
2149 
2150     /** Save a .txt file of various test results. */
saveTextToFile(Uri uri, String outputText)2151     private void saveTextToFile(Uri uri, String outputText) {
2152         ParcelFileDescriptor parcelFileDescriptor = null;
2153         FileOutputStream outputStream;
2154         try {
2155             parcelFileDescriptor = getApplicationContext().getContentResolver().
2156                                    openFileDescriptor(uri, "w");
2157 
2158             FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
2159             outputStream = new FileOutputStream(fileDescriptor);
2160             log("Done creating output stream");
2161 
2162             outputStream.write(outputText.getBytes());
2163             parcelFileDescriptor.close();
2164         } catch (Exception e) {
2165             log("Failed to open text file " + e);
2166         } finally {
2167             try {
2168                 if (parcelFileDescriptor != null) {
2169                     parcelFileDescriptor.close();
2170                 }
2171             } catch (Exception e) {
2172                 e.printStackTrace();
2173                 log("Error closing ParcelFile Descriptor");
2174             }
2175         }
2176     }
2177 
getReport()2178     private StringBuilder getReport() {
2179         String endline = "\n";
2180         final int stringLength = 300;
2181         StringBuilder sb = new StringBuilder(stringLength);
2182         sb.append("DateTime = " + mTestStartTimeString + endline);
2183         sb.append(INTENT_SAMPLING_FREQUENCY + " = " + mSamplingRate + endline);
2184         sb.append(INTENT_CHANNEL_INDEX + " = " + mChannelIndex + endline);
2185         sb.append(INTENT_RECORDER_BUFFER + " = " + mRecorderBufferSizeInBytes /
2186                 Constant.BYTES_PER_FRAME + endline);
2187         sb.append(INTENT_PLAYER_BUFFER + " = " + mPlayerBufferSizeInBytes /
2188                 Constant.BYTES_PER_FRAME + endline);
2189         sb.append(INTENT_AUDIO_THREAD + " = " + mAudioThreadType + endline);
2190 
2191         String audioType = audioThreadTypeToString(mAudioThreadType);
2192         sb.append(INTENT_AUDIO_THREAD + "_String = " + audioType + endline);
2193 
2194         sb.append(INTENT_MIC_SOURCE + " = " + mMicSource + endline);
2195         sb.append(INTENT_MIC_SOURCE + "_String = " + getApp().getMicSourceString(mMicSource)
2196                 + endline);
2197         sb.append(INTENT_AUDIO_LEVEL + " = " + mSoundLevel + endline);
2198 
2199         switch (mTestType) {
2200 
2201             case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
2202                 sb.append(INTENT_IGNORE_FIRST_FRAMES + " = " + mIgnoreFirstFrames + endline);
2203                 if (mCorrelation.isValid()) {
2204                     sb.append(String.format("LatencyMs = %.2f", mCorrelation.mEstimatedLatencyMs)
2205                             + endline);
2206                 } else {
2207                     sb.append(String.format("LatencyMs = unknown") + endline);
2208                 }
2209 
2210                 sb.append(String.format("LatencyConfidence = %.2f",
2211                         mCorrelation.mEstimatedLatencyConfidence) + endline);
2212 
2213                 sb.append(String.format("Average = %.4f", mCorrelation.mAverage) + endline);
2214                 sb.append(String.format("RMS = %.4f", mCorrelation.mRms) + endline);
2215                 break;
2216 
2217             case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
2218                 sb.append("Buffer Test Duration (s) = " + mBufferTestDurationInSeconds + endline);
2219 
2220                 // report recorder results
2221                 int[] recorderBufferData = null;
2222                 int recorderBufferDataMax = 0;
2223                 double recorderBufferDataStdDev = 0.0;
2224                 switch (mAudioThreadType) {
2225                     case Constant.AUDIO_THREAD_TYPE_JAVA:
2226                         recorderBufferData = mRecorderBufferPeriod.getBufferPeriodArray();
2227                         recorderBufferDataMax = mRecorderBufferPeriod.getMaxBufferPeriod();
2228                         recorderBufferDataStdDev = mRecorderBufferPeriod.getStdDevBufferPeriod();
2229                         break;
2230                     case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
2231                     case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
2232                         recorderBufferData = mNativeRecorderBufferPeriodArray;
2233                         recorderBufferDataMax = mNativeRecorderMaxBufferPeriod;
2234                         recorderBufferDataStdDev = mNativeRecorderStdDevBufferPeriod;
2235                         break;
2236                 }
2237                 // report expected recorder buffer period
2238                 if (recorderBufferData != null) {
2239                     // this is the range of data that actually has values
2240                     int usefulDataRange = Math.min(recorderBufferDataMax + 1,
2241                             recorderBufferData.length);
2242                     int[] usefulBufferData = Arrays.copyOfRange(recorderBufferData, 0,
2243                             usefulDataRange);
2244                     PerformanceMeasurement measurement = new PerformanceMeasurement(
2245                             mRecorderCallbackTimes.getExpectedBufferPeriod(), usefulBufferData);
2246                     double recorderPercentAtExpected =
2247                             measurement.percentBufferPeriodsAtExpected();
2248                     double benchmark = measurement.computeWeightedBenchmark();
2249                     int outliers = measurement.countOutliers();
2250                     sb.append("Expected Recorder Buffer Period (ms) = " +
2251                             mRecorderCallbackTimes.getExpectedBufferPeriod() + endline);
2252                     sb.append("Recorder Buffer Periods At Expected = " +
2253                             String.format("%.5f%%", recorderPercentAtExpected * 100) + endline);
2254 
2255                     sb.append("Recorder Buffer Period Std Dev = "
2256                             + String.format(Locale.US, "%.5f ms", recorderBufferDataStdDev)
2257                             + endline);
2258 
2259                     // output thousandths of a percent not at expected buffer period
2260                     sb.append("kth% Late Recorder Buffer Callbacks = "
2261                             + String.format("%.5f", (1 - recorderPercentAtExpected) * 100000)
2262                             + endline);
2263                     sb.append("Recorder Benchmark = " + benchmark + endline);
2264                     sb.append("Recorder Number of Outliers = " + outliers + endline);
2265                 } else {
2266                     sb.append("Cannot Find Recorder Buffer Period Data!" + endline);
2267                 }
2268 
2269                 // report player results
2270                 int[] playerBufferData = null;
2271                 int playerBufferDataMax = 0;
2272                 double playerBufferDataStdDev = 0.0;
2273                 switch (mAudioThreadType) {
2274                     case Constant.AUDIO_THREAD_TYPE_JAVA:
2275                         playerBufferData = mPlayerBufferPeriod.getBufferPeriodArray();
2276                         playerBufferDataMax = mPlayerBufferPeriod.getMaxBufferPeriod();
2277                         playerBufferDataStdDev = mPlayerBufferPeriod.getStdDevBufferPeriod();
2278                         break;
2279                     case Constant.AUDIO_THREAD_TYPE_NATIVE_SLES:
2280                     case Constant.AUDIO_THREAD_TYPE_NATIVE_AAUDIO:
2281                         playerBufferData = mNativePlayerBufferPeriodArray;
2282                         playerBufferDataMax = mNativePlayerMaxBufferPeriod;
2283                         playerBufferDataStdDev = mNativePlayerStdDevBufferPeriod;
2284                         break;
2285                 }
2286                 // report expected player buffer period
2287                 sb.append("Expected Player Buffer Period (ms) = " +
2288                         mPlayerCallbackTimes.getExpectedBufferPeriod() + endline);
2289                 if (playerBufferData != null) {
2290                     // this is the range of data that actually has values
2291                     int usefulDataRange = Math.min(playerBufferDataMax + 1,
2292                             playerBufferData.length);
2293                     int[] usefulBufferData = Arrays.copyOfRange(playerBufferData, 0,
2294                             usefulDataRange);
2295                     PerformanceMeasurement measurement = new PerformanceMeasurement(
2296                             mPlayerCallbackTimes.getExpectedBufferPeriod(), usefulBufferData);
2297                     double playerPercentAtExpected = measurement.percentBufferPeriodsAtExpected();
2298                     double benchmark = measurement.computeWeightedBenchmark();
2299                     int outliers = measurement.countOutliers();
2300                     sb.append("Player Buffer Periods At Expected = "
2301                             + String.format("%.5f%%", playerPercentAtExpected * 100) + endline);
2302 
2303                     sb.append("Player Buffer Period Std Dev = "
2304                             + String.format(Locale.US, "%.5f ms", playerBufferDataStdDev)
2305                             + endline);
2306 
2307                     // output thousandths of a percent not at expected buffer period
2308                     sb.append("kth% Late Player Buffer Callbacks = "
2309                             + String.format("%.5f", (1 - playerPercentAtExpected) * 100000)
2310                             + endline);
2311                     sb.append("Player Benchmark = " + benchmark + endline);
2312                     sb.append("Player Number of Outliers = " + outliers + endline);
2313 
2314                 } else {
2315                     sb.append("Cannot Find Player Buffer Period Data!" + endline);
2316                 }
2317                 // report glitches per hour
2318                 int numberOfGlitches = estimateNumberOfGlitches(mGlitchesData);
2319                 double testDurationInHours = mBufferTestElapsedSeconds
2320                         / (double) Constant.SECONDS_PER_HOUR;
2321 
2322                 // Report Glitches Per Hour if sufficient data available, ie at least half an hour
2323                 if (testDurationInHours >= .5) {
2324                     int glitchesPerHour = (int) Math.ceil(numberOfGlitches/testDurationInHours);
2325                     sb.append("Glitches Per Hour = " + glitchesPerHour + endline);
2326                 }
2327                 sb.append("Total Number of Glitches = " + numberOfGlitches + endline);
2328 
2329                 // report if the total glitching interval is too long
2330                 sb.append("Total glitching interval too long =  " +
2331                         mGlitchingIntervalTooLong);
2332 
2333                 sb.append("\nLate Player Callbacks = ");
2334                 sb.append(mPlayerCallbackTimes.getNumLateOrEarlyCallbacks());
2335                 sb.append("\nLate Player Callbacks Exceeded Capacity = ");
2336                 sb.append(mPlayerCallbackTimes.isCapacityExceeded());
2337                 sb.append("\nLate Recorder Callbacks = ");
2338                 sb.append(mRecorderCallbackTimes.getNumLateOrEarlyCallbacks());
2339                 sb.append("\nLate Recorder Callbacks Exceeded Capacity = ");
2340                 sb.append(mRecorderCallbackTimes.isCapacityExceeded());
2341                 sb.append("\n");
2342         }
2343 
2344 
2345         String info = getApp().getSystemInfo();
2346         sb.append("SystemInfo = " + info + endline);
2347 
2348         return sb;
2349     }
2350 
2351     /** Save a .txt file of of glitch occurrences in ms from beginning of test. */
saveGlitchOccurrences(Uri uri, int[] glitchesData)2352     private void saveGlitchOccurrences(Uri uri, int[] glitchesData) {
2353         ParcelFileDescriptor parcelFileDescriptor = null;
2354         FileOutputStream outputStream;
2355         try {
2356             parcelFileDescriptor = getApplicationContext().getContentResolver().
2357                     openFileDescriptor(uri, "w");
2358 
2359             FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
2360             outputStream = new FileOutputStream(fileDescriptor);
2361 
2362             log("Done creating output stream");
2363 
2364             outputStream.write(GlitchesStringBuilder.getGlitchStringForFile(mFFTSamplingSize,
2365                     mFFTOverlapSamples, glitchesData, mSamplingRate).getBytes());
2366         } catch (Exception e) {
2367             log("Failed to open text file " + e);
2368         } finally {
2369             try {
2370                 if (parcelFileDescriptor != null) {
2371                     parcelFileDescriptor.close();
2372                 }
2373             } catch (Exception e) {
2374                 e.printStackTrace();
2375                 log("Error closing ParcelFile Descriptor");
2376             }
2377         }
2378     }
2379 
2380     /**
2381      * Estimate the number of glitches. This version of estimation will count two consecutive
2382      * glitching intervals as one glitch. This is because two time intervals are partly overlapped.
2383      * Note: If the total glitching intervals exceed the length of glitchesData, this estimation
2384      * becomes incomplete. However, whether or not the total glitching interval is too long will
2385      * also be indicated, and in the case it's true, we know something went wrong.
2386      */
estimateNumberOfGlitches(int[] glitchesData)2387     private static int estimateNumberOfGlitches(int[] glitchesData) {
2388         final int discard = 10; // don't count glitches occurring at the first few FFT interval
2389         boolean isPreviousGlitch = false; // is there a glitch in previous interval or not
2390         int previousFFTInterval = -1;
2391         int count = 0;
2392         // if there are three consecutive glitches, the first two will be counted as one,
2393         // the third will be counted as another one
2394         for (int i = 0; i < glitchesData.length; i++) {
2395             if (glitchesData[i] > discard) {
2396                 if (glitchesData[i] == previousFFTInterval + 1 && isPreviousGlitch) {
2397                     isPreviousGlitch = false;
2398                     previousFFTInterval = glitchesData[i];
2399                 } else {
2400                     isPreviousGlitch = true;
2401                     previousFFTInterval = glitchesData[i];
2402                     count += 1;
2403                 }
2404             }
2405 
2406         }
2407 
2408         return count;
2409     }
2410 
2411 
2412     /**
2413      * Estimate the number of glitches. This version of estimation will count the whole consecutive
2414      * intervals as one glitch. This version is not currently used.
2415      * Note: If the total glitching intervals exceed the length of glitchesData, this estimation
2416      * becomes incomplete. However, whether or not the total glitching interval is too long will
2417      * also be indicated, and in the case it's true, we know something went wrong.
2418      */
estimateNumberOfGlitches2(int[] glitchesData)2419     private static int estimateNumberOfGlitches2(int[] glitchesData) {
2420         final int discard = 10; // don't count glitches occurring at the first few FFT interval
2421         int previousFFTInterval = -1;
2422         int count = 0;
2423         for (int i = 0; i < glitchesData.length; i++) {
2424             if (glitchesData[i] > discard) {
2425                 if (glitchesData[i] != previousFFTInterval + 1) {
2426                     count += 1;
2427                 }
2428                 previousFFTInterval = glitchesData[i];
2429             }
2430         }
2431         return count;
2432     }
2433 
2434     /**
2435      * Check whether we have the RECORD_AUDIO permission
2436      * @return true if we do
2437      */
hasRecordAudioPermission()2438     private boolean hasRecordAudioPermission() {
2439         boolean hasPermission = (ContextCompat.checkSelfPermission(this,
2440                 Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED);
2441 
2442         log("Has RECORD_AUDIO permission? " + hasPermission);
2443         return hasPermission;
2444     }
2445 
2446     /**
2447      * Requests the RECORD_AUDIO permission from the user
2448      */
requestRecordAudioPermission(int requestCode)2449     private void requestRecordAudioPermission(int requestCode) {
2450 
2451         String requiredPermission = Manifest.permission.RECORD_AUDIO;
2452 
2453         // If the user previously denied this permission then show a message explaining why
2454         // this permission is needed
2455         if (ActivityCompat.shouldShowRequestPermissionRationale(this,
2456                 requiredPermission)) {
2457 
2458             showToastImportant("This app needs to record audio through the microphone to test "+
2459                     "the device's performance");
2460         }
2461 
2462         // request the permission.
2463         ActivityCompat.requestPermissions(this, new String[]{requiredPermission}, requestCode);
2464     }
2465 
2466     @Override
onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)2467     public void onRequestPermissionsResult(int requestCode,
2468                                            String permissions[], int[] grantResults) {
2469 
2470         // Save all files or run requested test after being granted permissions
2471         if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
2472             if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_RESULTS ) {
2473                 saveAllTo(getFileNamePrefix());
2474             } else if (requestCode == PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE_SCRIPT ) {
2475                 AtraceScriptsWriter.writeScriptsToFile(this);
2476             } else if (requestCode == PERMISSIONS_REQUEST_RECORD_AUDIO_BUFFER) {
2477                 startBufferTest();
2478             } else if (requestCode == PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY) {
2479                 startLatencyTest();
2480             }
2481         }
2482     }
2483 
2484     /**
2485      * Check whether we have the WRITE_EXTERNAL_STORAGE permission
2486      *
2487      * @return true if we do
2488      */
hasWriteFilePermission()2489     private boolean hasWriteFilePermission() {
2490         boolean hasPermission = (ContextCompat.checkSelfPermission(this,
2491                 Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
2492 
2493         log("Has WRITE_EXTERNAL_STORAGE? " + hasPermission);
2494         return hasPermission;
2495     }
2496 
2497     /**
2498      * Requests the WRITE_EXTERNAL_STORAGE permission from the user
2499      */
requestWriteFilePermission(int requestCode)2500     private void requestWriteFilePermission(int requestCode) {
2501 
2502         String requiredPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
2503 
2504         // request the permission.
2505         ActivityCompat.requestPermissions(this, new String[]{requiredPermission}, requestCode);
2506     }
2507 
2508     /**
2509      * Receive results from save files DialogAlert and either save all files directly
2510      * or use filename dialog
2511      */
2512     @Override
onSaveDialogSelect(DialogFragment dialog, boolean saveWithoutDialog)2513     public void onSaveDialogSelect(DialogFragment dialog, boolean saveWithoutDialog) {
2514         if (saveWithoutDialog) {
2515             saveAllTo("loopback_" + mTestStartTimeString);
2516         } else {
2517             SaveFilesWithDialog();
2518         }
2519     }
2520 
restoreInstanceState(Bundle in)2521     private void restoreInstanceState(Bundle in) {
2522         mTestType = in.getInt("mTestType");
2523         mMicSource = in.getInt("mMicSource");
2524         mAudioThreadType = in.getInt("mAudioThreadType");
2525         mSamplingRate = in.getInt("mSamplingRate");
2526         mChannelIndex = in.getInt("mChannelIndex");
2527         mSoundLevel = in.getInt("mSoundLevel");
2528         mPlayerBufferSizeInBytes = in.getInt("mPlayerBufferSizeInBytes");
2529         mRecorderBufferSizeInBytes = in.getInt("mRecorderBufferSizeInBytes");
2530 
2531         mTestStartTimeString = in.getString("mTestStartTimeString");
2532 
2533         mGlitchesData = in.getIntArray("mGlitchesData");
2534         if (mGlitchesData != null) {
2535             mGlitchingIntervalTooLong = in.getBoolean("mGlitchingIntervalTooLong");
2536             mFFTSamplingSize = in.getInt("mFFTSamplingSize");
2537             mFFTOverlapSamples = in.getInt("mFFTOverlapSamples");
2538             mBufferTestStartTime = in.getLong("mBufferTestStartTime");
2539             mBufferTestElapsedSeconds = in.getInt("mBufferTestElapsedSeconds");
2540             mBufferTestDurationInSeconds = in.getInt("mBufferTestDurationInSeconds");
2541             mBufferTestWavePlotDurationInSeconds =
2542                     in.getInt("mBufferTestWavePlotDurationInSeconds");
2543 
2544             findViewById(R.id.glitchReportPanel).setVisibility(View.VISIBLE);
2545         }
2546 
2547         if (mRetainedFragment.getWaveData() != null) {
2548             mCorrelation = in.getParcelable("mCorrelation");
2549             mPlayerBufferPeriod = in.getParcelable("mPlayerBufferPeriod");
2550             mRecorderBufferPeriod = in.getParcelable("mRecorderBufferPeriod");
2551             mPlayerCallbackTimes = in.getParcelable("mPlayerCallbackTimes");
2552             mRecorderCallbackTimes = in.getParcelable("mRecorderCallbackTimes");
2553 
2554             mNativePlayerBufferPeriodArray = in.getIntArray("mNativePlayerBufferPeriodArray");
2555             mNativePlayerMaxBufferPeriod = in.getInt("mNativePlayerMaxBufferPeriod");
2556             mNativeRecorderBufferPeriodArray = in.getIntArray("mNativeRecorderBufferPeriodArray");
2557             mNativeRecorderMaxBufferPeriod = in.getInt("mNativeRecorderMaxBufferPeriod");
2558 
2559             mWavePlotView.setData(mRetainedFragment.getWaveData(), mSamplingRate);
2560             refreshState();
2561             findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE);
2562             findViewById(R.id.resultSummary).setVisibility(View.VISIBLE);
2563         }
2564     }
2565 
2566     @Override
onSaveInstanceState(Bundle out)2567     protected void onSaveInstanceState(Bundle out) {
2568         super.onSaveInstanceState(out);
2569 
2570         out.putInt("mTestType", mTestType);
2571         out.putInt("mMicSource", mMicSource);
2572         out.putInt("mAudioThreadType", mAudioThreadType);
2573         out.putInt("mSamplingRate", mSamplingRate);
2574         out.putInt("mChannelIndex", mChannelIndex);
2575         out.putInt("mSoundLevel", mSoundLevel);
2576         out.putInt("mPlayerBufferSizeInBytes", mPlayerBufferSizeInBytes);
2577         out.putInt("mRecorderBufferSizeInBytes", mRecorderBufferSizeInBytes);
2578         out.putString("mTestStartTimeString", mTestStartTimeString);
2579 
2580         out.putParcelable("mCorrelation", mCorrelation);
2581         out.putParcelable("mPlayerBufferPeriod", mPlayerBufferPeriod);
2582         out.putParcelable("mRecorderBufferPeriod", mRecorderBufferPeriod);
2583         out.putParcelable("mPlayerCallbackTimes", mPlayerCallbackTimes);
2584         out.putParcelable("mRecorderCallbackTimes", mRecorderCallbackTimes);
2585 
2586         out.putIntArray("mNativePlayerBufferPeriodArray", mNativePlayerBufferPeriodArray);
2587         out.putInt("mNativePlayerMaxBufferPeriod", mNativePlayerMaxBufferPeriod);
2588         out.putIntArray("mNativeRecorderBufferPeriodArray", mNativeRecorderBufferPeriodArray);
2589         out.putInt("mNativeRecorderMaxBufferPeriod", mNativeRecorderMaxBufferPeriod);
2590 
2591         // buffer test values
2592         out.putIntArray("mGlitchesData", mGlitchesData);
2593         out.putBoolean("mGlitchingIntervalTooLong", mGlitchingIntervalTooLong);
2594         out.putInt("mFFTSamplingSize", mFFTSamplingSize);
2595         out.putInt("mFFTOverlapSamples", mFFTOverlapSamples);
2596         out.putLong("mBufferTestStartTime", mBufferTestStartTime);
2597         out.putInt("mBufferTestElapsedSeconds", mBufferTestElapsedSeconds);
2598         out.putInt("mBufferTestDurationInSeconds", mBufferTestDurationInSeconds);
2599         out.putInt("mBufferTestWavePlotDurationInSeconds", mBufferTestWavePlotDurationInSeconds);
2600     }
2601 
waitForUsbRoute()2602     private void waitForUsbRoute() {
2603         log("Start checking for USB Route connection");
2604         AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
2605         long startTime = System.currentTimeMillis();
2606         int iter = 0;
2607         while (true) {
2608             if (System.currentTimeMillis() - startTime > 15 * 1000) {
2609                 log("15 Seconds has elapsed before USB_AUDIO_ROUTE is detected, continue test.");
2610                 break;
2611             }
2612             iter++;
2613             AudioDeviceInfo[] devices;
2614             boolean usb_available = false;
2615             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
2616                 devices = am.getDevices(AudioManager.GET_DEVICES_INPUTS);
2617                 for (AudioDeviceInfo devInfo : devices) {
2618 
2619                     if (devInfo.getType() != AudioDeviceInfo.TYPE_BUILTIN_MIC && devInfo.getType() != AudioDeviceInfo.TYPE_TELEPHONY) {
2620                         log(" USB Check iteration: " + String.valueOf(iter));
2621                         log(" USB Check get type: " + String.valueOf(devInfo.getType()));
2622                     }
2623                     if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
2624                             devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
2625                         log(" USB Headset detected, continue test");
2626                         usb_available = true;
2627                         break;
2628                     }
2629                 }
2630 
2631             } else {
2632                 log("This system version does not support USB Audio Route check, continue test");
2633                 break;
2634             }
2635 
2636             log(" USB-> Check MediaRoute");
2637             UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
2638             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
2639                 HashMap<String, UsbDevice> usbDevices = manager.getDeviceList();
2640                 for (Map.Entry<String, UsbDevice> entry : usbDevices.entrySet()) {
2641                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
2642                         if (entry.getValue().getProductName().contains("USB Audio")) {
2643                             log(" USB Headset detected inside UsbManager, continue test");
2644                             usb_available = true;
2645                             log(" USB list: key " + entry.getKey() + " and value: " + String.valueOf(entry.getValue()));
2646                             break;
2647                         }
2648                     }
2649                 }
2650 
2651             }
2652             if (usb_available) {
2653                 long elapsed = System.currentTimeMillis() - startTime;
2654                 log("USB detection takes " + String.valueOf(elapsed) + " ms");
2655                 break;
2656             }
2657             try {
2658                 Thread.sleep(500);
2659             } catch (InterruptedException e) {
2660                 log("Got interrupted during USB Audio Route check");
2661                 e.printStackTrace();
2662             }
2663         }
2664     }
2665 
2666 }
2667