xref: /aosp_15_r20/external/oboe/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestAudioActivity.java (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.mobileer.oboetester;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.media.AudioAttributes;
25 import android.media.AudioDeviceInfo;
26 import android.media.AudioManager;
27 import android.os.Build;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.util.Log;
32 import android.view.View;
33 import android.view.WindowManager;
34 import android.widget.AdapterView;
35 import android.widget.Button;
36 import android.widget.CheckBox;
37 import android.widget.Spinner;
38 import android.widget.Toast;
39 import androidx.annotation.NonNull;
40 
41 import java.io.File;
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.Locale;
45 
46 /**
47  * Base class for other Activities.
48  */
49 abstract class TestAudioActivity extends Activity {
50     public static final String TAG = "OboeTester";
51 
52     protected static final int FADER_PROGRESS_MAX = 1000;
53     private static final int INTENT_TEST_DELAY_MILLIS = 1100;
54 
55     public static final int AUDIO_STATE_OPEN = 0;
56     public static final int AUDIO_STATE_STARTED = 1;
57     public static final int AUDIO_STATE_PAUSED = 2;
58     public static final int AUDIO_STATE_FLUSHED = 3;
59     public static final int AUDIO_STATE_STOPPED = 4;
60     public static final int AUDIO_STATE_RELEASED = 5;
61     public static final int AUDIO_STATE_CLOSING = 6;
62     public static final int AUDIO_STATE_CLOSED = 7;
63 
64     public static final int COLOR_ACTIVE = 0xFFD0D0A0;
65     public static final int COLOR_IDLE = 0xFFD0D0D0;
66 
67     // Pass the activity index to native so it can know how to respond to the start and stop calls.
68     // WARNING - must match definitions in NativeAudioContext.h ActivityType
69     public static final int ACTIVITY_TEST_OUTPUT = 0;
70     public static final int ACTIVITY_TEST_INPUT = 1;
71     public static final int ACTIVITY_TAP_TO_TONE = 2;
72     public static final int ACTIVITY_RECORD_PLAY = 3;
73     public static final int ACTIVITY_ECHO = 4;
74     public static final int ACTIVITY_RT_LATENCY = 5;
75     public static final int ACTIVITY_GLITCHES = 6;
76     public static final int ACTIVITY_TEST_DISCONNECT = 7;
77     public static final int ACTIVITY_DATA_PATHS = 8;
78     public static final int ACTIVITY_DYNAMIC_WORKLOAD = 9;
79 
80     private int mAudioState = AUDIO_STATE_CLOSED;
81 
82     protected ArrayList<StreamContext> mStreamContexts;
83     private Button mOpenButton;
84     private Button mStartButton;
85     private Button mPauseButton;
86     private Button mFlushButton;
87     private Button mStopButton;
88     private Button mReleaseButton;
89     private Button mCloseButton;
90     private MyStreamSniffer mStreamSniffer;
91     private CheckBox mCallbackReturnStopBox;
92     private Spinner mHangTimeSpinner;
93 
94     // Only set in some activities
95     protected CommunicationDeviceView mCommunicationDeviceView;
96     private int mSampleRate;
97     private int mSingleTestIndex = -1;
98     private static boolean mBackgroundEnabled;
99 
100     protected Bundle mBundleFromIntent;
101     protected boolean mTestRunningByIntent;
102     protected String mResultFileName;
103     private String mTestResults;
104     private ExternalFileWriter mExternalFileWriter = new ExternalFileWriter(this);
105 
getTestName()106     public String getTestName() {
107         return "TestAudio";
108     }
109 
110     public static class StreamContext {
111         StreamConfigurationView configurationView;
112         AudioStreamTester tester;
113 
isInput()114         boolean isInput() {
115             return tester.getCurrentAudioStream().isInput();
116         }
117     }
118 
119     // Periodically query the status of the streams.
120     protected class MyStreamSniffer {
121         public static final int SNIFFER_UPDATE_PERIOD_MSEC = 150;
122         public static final int SNIFFER_UPDATE_DELAY_MSEC = 300;
123 
124         private Handler mHandler;
125 
126         // Display status info for the stream.
127         private Runnable runnableCode = new Runnable() {
128             @Override
129             public void run() {
130                 boolean streamClosed = false;
131                 boolean gotViews = false;
132                 for (StreamContext streamContext : mStreamContexts) {
133                     AudioStreamBase.StreamStatus status = streamContext.tester.getCurrentAudioStream().getStreamStatus();
134                     AudioStreamBase.DoubleStatistics latencyStatistics =
135                             streamContext.tester.getCurrentAudioStream().getLatencyStatistics();
136                     if (streamContext.configurationView != null) {
137                         // Handler runs this on the main UI thread.
138                         int framesPerBurst = streamContext.tester.getCurrentAudioStream().getFramesPerBurst();
139                         status.framesPerCallback = getFramesPerCallback();
140                         String msg = "";
141                         msg += "timestamp.latency = " + latencyStatistics.dump() + "\n";
142                         msg += status.dump(framesPerBurst);
143                         streamContext.configurationView.setStatusText(msg);
144                         updateStreamDisplay();
145                         gotViews = true;
146                     }
147 
148                     streamClosed = streamClosed || (status.state >= 12);
149                 }
150 
151                 if (streamClosed) {
152                     onStreamClosed();
153                 } else {
154                     // Repeat this runnable code block again.
155                     if (gotViews) {
156                         mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC);
157                     }
158                 }
159             }
160         };
161 
startStreamSniffer()162         private void startStreamSniffer() {
163             stopStreamSniffer();
164             mHandler = new Handler(Looper.getMainLooper());
165             // Start the initial runnable task by posting through the handler
166             mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC);
167         }
168 
stopStreamSniffer()169         private void stopStreamSniffer() {
170             if (mHandler != null) {
171                 mHandler.removeCallbacks(runnableCode);
172             }
173         }
174 
175     }
176 
setBackgroundEnabled(boolean enabled)177     public static void setBackgroundEnabled(boolean enabled) {
178         mBackgroundEnabled = enabled;
179     }
180 
isBackgroundEnabled()181     public static boolean isBackgroundEnabled() {
182         return mBackgroundEnabled;
183     }
184 
onStreamClosed()185     public void onStreamClosed() {
186     }
187 
inflateActivity()188     protected abstract void inflateActivity();
189 
updateStreamDisplay()190     void updateStreamDisplay() {
191     }
192 
193     @Override
onCreate(Bundle savedInstanceState)194     protected void onCreate(Bundle savedInstanceState) {
195         super.onCreate(savedInstanceState);
196         inflateActivity();
197         findAudioCommon();
198 
199         mBundleFromIntent = getIntent().getExtras();
200     }
201 
202     @Override
onNewIntent(Intent intent)203     public void onNewIntent(Intent intent) {
204         mBundleFromIntent = intent.getExtras();
205     }
206 
isTestConfiguredUsingBundle()207     public boolean isTestConfiguredUsingBundle() {
208         return mBundleFromIntent != null;
209     }
210 
hideSettingsViews()211     public void hideSettingsViews() {
212         for (StreamContext streamContext : mStreamContexts) {
213             if (streamContext.configurationView != null) {
214                 streamContext.configurationView.hideSettingsView();
215             }
216         }
217     }
218 
getActivityType()219     abstract int getActivityType();
220 
setSingleTestIndex(int testIndex)221     public void setSingleTestIndex(int testIndex) {
222         mSingleTestIndex = testIndex;
223     }
224 
getSingleTestIndex()225     public int getSingleTestIndex() {
226         return mSingleTestIndex;
227     }
228 
229     @Override
onStart()230     protected void onStart() {
231         super.onStart();
232         resetConfiguration();
233         setActivityType(getActivityType());
234         // TODO Use LifeCycleObserver instead of this.
235         if (mCommunicationDeviceView != null) {
236             mCommunicationDeviceView.onStart();
237         }
238     }
239 
resetConfiguration()240     protected void resetConfiguration() {
241     }
242 
243     @Override
onResume()244     public void onResume() {
245         super.onResume();
246         if (mBundleFromIntent != null) {
247             processBundleFromIntent();
248         }
249     }
250 
setVolumeFromIntent()251     private void setVolumeFromIntent() {
252         float normalizedVolume = IntentBasedTestSupport.getNormalizedVolumeFromBundle(mBundleFromIntent);
253         if (normalizedVolume >= 0.0) {
254             int streamType = IntentBasedTestSupport.getVolumeStreamTypeFromBundle(mBundleFromIntent);
255             AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
256             int maxVolume = audioManager.getStreamMaxVolume(streamType);
257             int requestedVolume = (int) (maxVolume * normalizedVolume);
258             audioManager.setStreamVolume(streamType, requestedVolume, 0);
259         }
260     }
261 
processBundleFromIntent()262     private void processBundleFromIntent() {
263         if (mTestRunningByIntent) {
264             return;
265         }
266 
267         // Delay the test start to avoid race conditions. See Oboe Issue #1533
268         mTestRunningByIntent = true;
269         Handler handler = new Handler(Looper.getMainLooper()); // UI thread
270         handler.postDelayed(new DelayedTestByIntentRunnable(),
271                 INTENT_TEST_DELAY_MILLIS); // Delay long enough to get past the onStop() call!
272 
273     }
274 
275     private class DelayedTestByIntentRunnable implements Runnable {
276         @Override
run()277         public void run() {
278             try {
279                 mResultFileName = mBundleFromIntent.getString(IntentBasedTestSupport.KEY_FILE_NAME);
280                 setVolumeFromIntent();
281                 startTestUsingBundle();
282             } catch( Exception e) {
283                 showErrorToast(e.getMessage());
284             }
285         }
286     }
287 
startTestUsingBundle()288     public void startTestUsingBundle() {
289     }
290 
291     @Override
onPause()292     protected void onPause() {
293         super.onPause();
294     }
295 
296     @Override
onStop()297     protected void onStop() {
298         if (!isBackgroundEnabled()) {
299             Log.i(TAG, "onStop() called so stop the test =========================");
300             onStopTest();
301         }
302         if (mCommunicationDeviceView != null) {
303             mCommunicationDeviceView.onStop();
304         }
305         super.onStop();
306     }
307 
308     @Override
onDestroy()309     protected void onDestroy() {
310         if (isBackgroundEnabled()) {
311             Log.i(TAG, "onDestroy() called so stop the test =========================");
312             onStopTest();
313         }
314         mAudioState = AUDIO_STATE_CLOSED;
315         super.onDestroy();
316     }
317 
updateEnabledWidgets()318     protected void updateEnabledWidgets() {
319         if (mOpenButton != null) {
320             mOpenButton.setBackgroundColor(mAudioState == AUDIO_STATE_OPEN ? COLOR_ACTIVE : COLOR_IDLE);
321             mStartButton.setBackgroundColor(mAudioState == AUDIO_STATE_STARTED ? COLOR_ACTIVE : COLOR_IDLE);
322             mPauseButton.setBackgroundColor(mAudioState == AUDIO_STATE_PAUSED ? COLOR_ACTIVE : COLOR_IDLE);
323             mFlushButton.setBackgroundColor(mAudioState == AUDIO_STATE_FLUSHED ? COLOR_ACTIVE : COLOR_IDLE);
324             mStopButton.setBackgroundColor(mAudioState == AUDIO_STATE_STOPPED ? COLOR_ACTIVE : COLOR_IDLE);
325             mReleaseButton.setBackgroundColor(mAudioState == AUDIO_STATE_RELEASED ? COLOR_ACTIVE : COLOR_IDLE);
326             mCloseButton.setBackgroundColor(mAudioState == AUDIO_STATE_CLOSED ? COLOR_ACTIVE : COLOR_IDLE);
327         }
328         setConfigViewsEnabled(mAudioState == AUDIO_STATE_CLOSED);
329     }
330 
setConfigViewsEnabled(boolean b)331     private void setConfigViewsEnabled(boolean b) {
332         for (StreamContext streamContext : mStreamContexts) {
333             if (streamContext.configurationView != null) {
334                 streamContext.configurationView.setChildrenEnabled(b);
335             }
336         }
337     }
338 
applyConfigurationViewsToModels()339     private void applyConfigurationViewsToModels() {
340         for (StreamContext streamContext : mStreamContexts) {
341             if (streamContext.configurationView != null) {
342                 streamContext.configurationView.applyToModel(streamContext.tester.requestedConfiguration);
343             }
344         }
345     }
346 
isOutput()347     abstract boolean isOutput();
348 
clearStreamContexts()349     public void clearStreamContexts() {
350         mStreamContexts.clear();
351     }
352 
addOutputStreamContext()353     public StreamContext addOutputStreamContext() {
354         StreamContext streamContext = new StreamContext();
355         streamContext.tester = AudioOutputTester.getInstance();
356         streamContext.configurationView = (StreamConfigurationView)
357                 findViewById(R.id.outputStreamConfiguration);
358         if (streamContext.configurationView == null) {
359             streamContext.configurationView = (StreamConfigurationView)
360                     findViewById(R.id.streamConfiguration);
361         }
362         if (streamContext.configurationView != null) {
363             streamContext.configurationView.setOutput(true);
364         }
365         mStreamContexts.add(streamContext);
366         return streamContext;
367     }
368 
addAudioOutputTester()369     public AudioOutputTester addAudioOutputTester() {
370         StreamContext streamContext = addOutputStreamContext();
371         return (AudioOutputTester) streamContext.tester;
372     }
373 
addInputStreamContext()374     public StreamContext addInputStreamContext() {
375         StreamContext streamContext = new StreamContext();
376         streamContext.tester = AudioInputTester.getInstance();
377         streamContext.configurationView = (StreamConfigurationView)
378                 findViewById(R.id.inputStreamConfiguration);
379         if (streamContext.configurationView == null) {
380             streamContext.configurationView = (StreamConfigurationView)
381                     findViewById(R.id.streamConfiguration);
382         }
383         if (streamContext.configurationView != null) {
384             streamContext.configurationView.setOutput(false);
385         }
386         mStreamContexts.add(streamContext);
387         return streamContext;
388     }
389 
addAudioInputTester()390     public AudioInputTester addAudioInputTester() {
391         StreamContext streamContext = addInputStreamContext();
392         return (AudioInputTester) streamContext.tester;
393     }
394 
updateStreamConfigurationViews()395     void updateStreamConfigurationViews() {
396         for (StreamContext streamContext : mStreamContexts) {
397             if (streamContext.configurationView != null) {
398                 streamContext.configurationView.updateDisplay(streamContext.tester.actualConfiguration);
399             }
400         }
401     }
402 
getFirstInputStreamContext()403     StreamContext getFirstInputStreamContext() {
404         for (StreamContext streamContext : mStreamContexts) {
405             if (streamContext.isInput())
406                 return streamContext;
407         }
408         return null;
409     }
410 
getFirstOutputStreamContext()411     StreamContext getFirstOutputStreamContext() {
412         for (StreamContext streamContext : mStreamContexts) {
413             if (!streamContext.isInput())
414                 return streamContext;
415         }
416         return null;
417     }
418 
findAudioCommon()419     protected void findAudioCommon() {
420         mOpenButton = (Button) findViewById(R.id.button_open);
421         if (mOpenButton != null) {
422             mStartButton = (Button) findViewById(R.id.button_start);
423             mPauseButton = (Button) findViewById(R.id.button_pause);
424             mFlushButton = (Button) findViewById(R.id.button_flush);
425             mStopButton = (Button) findViewById(R.id.button_stop);
426             mReleaseButton = (Button) findViewById(R.id.button_release);
427             mCloseButton = (Button) findViewById(R.id.button_close);
428         }
429         mStreamContexts = new ArrayList<StreamContext>();
430 
431         mCallbackReturnStopBox = (CheckBox) findViewById(R.id.callbackReturnStop);
432         if (mCallbackReturnStopBox != null) {
433             mCallbackReturnStopBox.setOnClickListener(new View.OnClickListener() {
434                 @Override
435                 public void onClick(View v) {
436                     OboeAudioStream.setCallbackReturnStop(mCallbackReturnStopBox.isChecked());
437                 }
438             });
439         }
440         OboeAudioStream.setCallbackReturnStop(false);
441 
442         mHangTimeSpinner = (Spinner) findViewById(R.id.spinner_hang_time);
443         if (mHangTimeSpinner != null) {
444             mHangTimeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
445                 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
446                     String hangTimeText = (String) mHangTimeSpinner.getAdapter().getItem(position);
447                     int hangTimeMillis = Integer.parseInt(hangTimeText);
448                     Log.d(TAG, "Hang Time = " + hangTimeMillis + " msec");
449 
450                     OboeAudioStream.setHangTimeMillis(hangTimeMillis);
451                 }
452 
453                 public void onNothingSelected(AdapterView<?> parent) {
454                     OboeAudioStream.setHangTimeMillis(0);
455                 }
456             });
457         }
458         OboeAudioStream.setHangTimeMillis(0);
459 
460         mStreamSniffer = new MyStreamSniffer();
461     }
462 
updateNativeAudioParameters()463     private void updateNativeAudioParameters() {
464         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
465             AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
466             String text = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
467             int audioManagerSampleRate = Integer.parseInt(text);
468             text = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
469             int audioManagerFramesPerBurst = Integer.parseInt(text);
470             setDefaultAudioValues(audioManagerSampleRate, audioManagerFramesPerBurst);
471         }
472     }
473 
showErrorToast(String message)474     protected void showErrorToast(String message) {
475         Log.e(TAG, "showErrorToast(\"" + message + "\")");
476         String text = "Error: " + message;
477         showToast(text);
478     }
479 
showToast(final String message)480     protected void showToast(final String message) {
481         runOnUiThread(new Runnable() {
482             @Override
483             public void run() {
484                 Toast.makeText(TestAudioActivity.this,
485                         message,
486                         Toast.LENGTH_SHORT).show();
487             }
488         });
489     }
490 
onStartAllContexts()491     private void onStartAllContexts() {
492         for (StreamContext streamContext : mStreamContexts) {
493             streamContext.tester.getCurrentAudioStream().onStart();
494         }
495     }
onStopAllContexts()496     private void onStopAllContexts() {
497         for (StreamContext streamContext : mStreamContexts) {
498             streamContext.tester.getCurrentAudioStream().onStop();
499         }
500     }
501 
openAudio(View view)502     public void openAudio(View view) {
503         try {
504             openAudio();
505         } catch (Exception e) {
506             showErrorToast("openAudio() caught " + e.getMessage());
507         }
508     }
509 
clearHangTime()510     void clearHangTime() {
511         OboeAudioStream.setHangTimeMillis(0);
512         if (mHangTimeSpinner != null) {
513             mHangTimeSpinner.setSelection(0);
514         }
515     }
516 
startAudio(View view)517     public void startAudio(View view) {
518         Log.i(TAG, "startAudio() called =======================================");
519         clearHangTime(); // start running
520         try {
521             startAudio();
522         } catch (Exception e) {
523             showErrorToast("startAudio() caught " + e.getMessage());
524         }
525         keepScreenOn(true);
526     }
527 
keepScreenOn(boolean on)528     protected void keepScreenOn(boolean on) {
529         if (on) {
530             getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
531         } else {
532             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
533         }
534     }
535 
stopAudio(View view)536     public void stopAudio(View view) {
537         stopAudio();
538         keepScreenOn(false);
539     }
540 
pauseAudio(View view)541     public void pauseAudio(View view) {
542         pauseAudio();
543         keepScreenOn(false);
544     }
545 
flushAudio(View view)546     public void flushAudio(View view) {
547         flushAudio();
548     }
549 
closeAudio(View view)550     public void closeAudio(View view) {
551         closeAudio();
552     }
553 
releaseAudio(View view)554     public void releaseAudio(View view) {
555         releaseAudio();
556     }
557 
getSampleRate()558     public int getSampleRate() {
559         return mSampleRate;
560     }
561 
openAudio()562     public void openAudio() throws IOException {
563         closeAudio();
564 
565         updateNativeAudioParameters();
566 
567         if (!isTestConfiguredUsingBundle()) {
568             applyConfigurationViewsToModels();
569         }
570 
571         int sampleRate = 0; // Use the OUTPUT sample rate for INPUT
572 
573         // Open output streams then open input streams.
574         // This is so that the capacity of input stream can be expanded to
575         // match the burst size of the output for full duplex.
576         for (StreamContext streamContext : mStreamContexts) {
577             if (!streamContext.isInput()) { // OUTPUT?
578                 openStreamContext(streamContext);
579                 int streamSampleRate = streamContext.tester.actualConfiguration.getSampleRate();
580                 if (sampleRate == 0) {
581                     sampleRate = streamSampleRate;
582                 }
583 
584                 if (shouldSetStreamControlByAttributes()) {
585                     // Associate volume keys with this output stream.
586                     int actualUsage = streamContext.tester.actualConfiguration.getUsage();
587                     int actualContentType = streamContext.tester.actualConfiguration.getContentType();
588                     setStreamControlByAttributes(actualUsage, actualContentType);
589                 }
590             }
591         }
592         for (StreamContext streamContext : mStreamContexts) {
593             if (streamContext.isInput()) {
594                 if (sampleRate != 0) {
595                     streamContext.tester.requestedConfiguration.setSampleRate(sampleRate);
596                 }
597                 openStreamContext(streamContext);
598             }
599         }
600         updateEnabledWidgets();
601         onStartAllContexts();
602         mStreamSniffer.startStreamSniffer();
603     }
604 
shouldSetStreamControlByAttributes()605     protected boolean shouldSetStreamControlByAttributes() {
606         return true;
607     }
608 
609     /**
610      * Associate the volume keys with the stream we are playing.
611      * @param usage usage for the stream
612      * @param contentType tupe of the stream
613      */
setStreamControlByAttributes(int usage, int contentType)614     private void setStreamControlByAttributes(int usage, int contentType) {
615         AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage)
616                 .setContentType(contentType).build();
617         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
618             int volumeControlStream = attributes.getVolumeControlStream();
619             Log.i(TAG, "setVolumeControlStream(" + volumeControlStream + ")");
620             setVolumeControlStream(volumeControlStream);
621         }
622     }
623 
624     /**
625      * @param deviceId
626      * @return true if the device is TYPE_BLUETOOTH_SCO
627      */
isScoDevice(int deviceId)628     boolean isScoDevice(int deviceId) {
629         if (deviceId == 0) return false; // Unspecified
630         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
631         final AudioDeviceInfo[] devices = audioManager.getDevices(
632                 AudioManager.GET_DEVICES_INPUTS | AudioManager.GET_DEVICES_OUTPUTS);
633         for (AudioDeviceInfo device : devices) {
634             if (device.getId() == deviceId) {
635                 return device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
636             }
637         }
638         return false;
639     }
640 
openStreamContext(StreamContext streamContext)641     private void openStreamContext(StreamContext streamContext) throws IOException {
642         StreamConfiguration requestedConfig = streamContext.tester.requestedConfiguration;
643         StreamConfiguration actualConfig = streamContext.tester.actualConfiguration;
644 
645         streamContext.tester.open(); // OPEN the stream
646 
647         mSampleRate = actualConfig.getSampleRate();
648         mAudioState = AUDIO_STATE_OPEN;
649         int sessionId = actualConfig.getSessionId();
650         if (streamContext.configurationView != null) {
651             if (sessionId > 0) {
652                 try {
653                     streamContext.configurationView.setupEffects(sessionId);
654                 } catch (Exception e) {
655                     showErrorToast("openStreamContext() caught " + e.getMessage());
656                 }
657             }
658             streamContext.configurationView.updateDisplay(streamContext.tester.actualConfiguration);
659         }
660     }
661 
662     // Native methods
startNative()663     private native int startNative();
664 
pauseNative()665     private native int pauseNative();
666 
flushNative()667     private native int flushNative();
668 
stopNative()669     private native int stopNative();
670 
releaseNative()671     private native int releaseNative();
672 
setActivityType(int activityType)673     protected native void setActivityType(int activityType);
674 
getFramesPerCallback()675     private native int getFramesPerCallback();
676 
setUseAlternativeAdpf(boolean enabled)677     public native void setUseAlternativeAdpf(boolean enabled);
678 
setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst)679     private static native void setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst);
680 
startAudio()681     public void startAudio() throws IOException {
682         Log.i(TAG, "startAudio() called =========================");
683         int result = startNative();
684         if (result != 0) {
685             showErrorToast("Start failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
686             throw new IOException("startNative returned " + result + ", " + StreamConfiguration.convertErrorToText(result));
687         } else {
688             onStartAllContexts();
689             for (StreamContext streamContext : mStreamContexts) {
690                 StreamConfigurationView configView = streamContext.configurationView;
691                 if (configView != null) {
692                     configView.updateDisplay(streamContext.tester.actualConfiguration);
693                 }
694             }
695             mAudioState = AUDIO_STATE_STARTED;
696             updateEnabledWidgets();
697         }
698     }
699 
toastPauseError(int result)700     protected void toastPauseError(int result) {
701         showErrorToast("Pause failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
702     }
703 
pauseAudio()704     public void pauseAudio() {
705         int result = pauseNative();
706         if (result != 0) {
707             toastPauseError(result);
708         } else {
709             mAudioState = AUDIO_STATE_PAUSED;
710             updateEnabledWidgets();
711             onStopAllContexts();
712         }
713     }
714 
flushAudio()715     public void flushAudio() {
716         int result = flushNative();
717         if (result != 0) {
718             showErrorToast("Flush failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
719         } else {
720             mAudioState = AUDIO_STATE_FLUSHED;
721             updateEnabledWidgets();
722         }
723     }
724 
stopAudio()725     public void stopAudio() {
726         int result = stopNative();
727         if (result != 0) {
728             showErrorToast("Stop failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
729         } else {
730             mAudioState = AUDIO_STATE_STOPPED;
731             updateEnabledWidgets();
732             onStopAllContexts();
733         }
734     }
735 
releaseAudio()736     public void releaseAudio() {
737         int result = releaseNative();
738         if (result != 0) {
739             showErrorToast("Release failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
740         } else {
741             mAudioState = AUDIO_STATE_RELEASED;
742             updateEnabledWidgets();
743             onStopAllContexts();
744         }
745     }
746 
runTest()747     public void runTest() {
748     }
749 
saveIntentLog()750     public void saveIntentLog() {
751     }
752 
753     // This should only be called from UI events such as onStop or a button press.
onStopTest()754     public void onStopTest() {
755         stopTest();
756     }
757 
stopTest()758     public void stopTest() {
759         stopAudio();
760         closeAudio();
761     }
762 
stopAudioQuiet()763     public void stopAudioQuiet() {
764         stopNative();
765         mAudioState = AUDIO_STATE_STOPPED;
766         updateEnabledWidgets();
767     }
768 
769     // Make synchronized so we don't close from two streams at the same time.
closeAudio()770     public synchronized void closeAudio() {
771         if (mAudioState >= AUDIO_STATE_CLOSING) {
772             Log.d(TAG, "closeAudio() already closing");
773             return;
774         }
775         mAudioState = AUDIO_STATE_CLOSING;
776 
777         mStreamSniffer.stopStreamSniffer();
778         // Close output streams first because legacy callbacks may still be active
779         // and an output stream may be calling the input stream.
780         for (StreamContext streamContext : mStreamContexts) {
781             if (!streamContext.isInput()) {
782                 streamContext.tester.close();
783             }
784         }
785         for (StreamContext streamContext : mStreamContexts) {
786             if (streamContext.isInput()) {
787                 streamContext.tester.close();
788             }
789         }
790 
791         mAudioState = AUDIO_STATE_CLOSED;
792         updateEnabledWidgets();
793     }
794 
startBluetoothSco()795     void startBluetoothSco() {
796         AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
797         myAudioMgr.startBluetoothSco();
798     }
799 
stopBluetoothSco()800     void stopBluetoothSco() {
801         AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
802         myAudioMgr.stopBluetoothSco();
803     }
804 
805     @NonNull
getCommonTestReport()806     protected String getCommonTestReport() {
807         StringBuffer report = new StringBuffer();
808         // Add some extra information for the remote tester.
809         report.append("build.fingerprint = " + Build.FINGERPRINT + "\n");
810         try {
811             PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
812             report.append(String.format(Locale.getDefault(), "test.version = %s\n", pinfo.versionName));
813             report.append(String.format(Locale.getDefault(), "test.version.code = %d\n", pinfo.versionCode));
814         } catch (PackageManager.NameNotFoundException e) {
815         }
816         report.append("time.millis = " + System.currentTimeMillis() + "\n");
817 
818         if (mStreamContexts.size() == 0) {
819             report.append("ERROR: no active streams" + "\n");
820         } else {
821             StreamContext streamContext = mStreamContexts.get(0);
822             AudioStreamTester streamTester = streamContext.tester;
823             report.append(streamTester.actualConfiguration.dump());
824             AudioStreamBase.StreamStatus status = streamTester.getCurrentAudioStream().getStreamStatus();
825             AudioStreamBase.DoubleStatistics latencyStatistics =
826                     streamTester.getCurrentAudioStream().getLatencyStatistics();
827             int framesPerBurst = streamTester.getCurrentAudioStream().getFramesPerBurst();
828             status.framesPerCallback = getFramesPerCallback();
829             report.append("timestamp.latency = " + latencyStatistics.dump() + "\n");
830             report.append(status.dump(framesPerBurst));
831         }
832 
833         return report.toString();
834     }
835 
maybeWriteTestResult(String resultString)836     File maybeWriteTestResult(String resultString) {
837         File fileWritten = null;
838         if (mResultFileName != null) {
839             try {
840                 fileWritten = mExternalFileWriter.writeStringToExternalFile(resultString, mResultFileName);
841             } catch (IOException e) {
842                 e.printStackTrace();
843                 showErrorToast(" writing result file. " + e.getMessage());
844             }
845             mResultFileName = null;
846         }
847         return fileWritten;
848     }
849 }
850