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