• 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 android.Manifest;
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
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.support.annotation.NonNull;
32 import android.support.v4.app.ActivityCompat;
33 import android.support.v4.content.ContextCompat;
34 import android.util.Log;
35 import android.view.View;
36 import android.view.WindowManager;
37 import android.widget.Button;
38 import android.widget.CheckBox;
39 import android.widget.Toast;
40 
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.OutputStreamWriter;
45 import java.io.Writer;
46 import java.util.ArrayList;
47 
48 /**
49  * Base class for other Activities.
50  */
51 abstract class TestAudioActivity extends Activity {
52     public static final String TAG = "OboeTester";
53 
54     protected static final int FADER_PROGRESS_MAX = 1000;
55     private static final int INTENT_TEST_DELAY_MILLIS = 1100;
56 
57     public static final int AUDIO_STATE_OPEN = 0;
58     public static final int AUDIO_STATE_STARTED = 1;
59     public static final int AUDIO_STATE_PAUSED = 2;
60     public static final int AUDIO_STATE_STOPPED = 3;
61     public static final int AUDIO_STATE_CLOSING = 4;
62     public static final int AUDIO_STATE_CLOSED = 5;
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 
79     private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 1001;
80 
81     private int mAudioState = AUDIO_STATE_CLOSED;
82 
83     protected ArrayList<StreamContext> mStreamContexts;
84     private Button mOpenButton;
85     private Button mStartButton;
86     private Button mPauseButton;
87     private Button mStopButton;
88     private Button mCloseButton;
89     private MyStreamSniffer mStreamSniffer;
90     private CheckBox mCallbackReturnStopBox;
91     private int mSampleRate;
92     private int mSingleTestIndex = -1;
93     private static boolean mBackgroundEnabled;
94 
95     protected Bundle mBundleFromIntent;
96     protected boolean mTestRunningByIntent;
97     protected String mResultFileName;
98     private String mTestResults;
99 
getTestName()100     public String getTestName() {
101         return "TestAudio";
102     }
103 
104     public static class StreamContext {
105         StreamConfigurationView configurationView;
106         AudioStreamTester tester;
107 
isInput()108         boolean isInput() {
109             return tester.getCurrentAudioStream().isInput();
110         }
111     }
112 
113     // Periodically query the status of the streams.
114     protected class MyStreamSniffer {
115         public static final int SNIFFER_UPDATE_PERIOD_MSEC = 150;
116         public static final int SNIFFER_UPDATE_DELAY_MSEC = 300;
117 
118         private Handler mHandler;
119 
120         // Display status info for the stream.
121         private Runnable runnableCode = new Runnable() {
122             @Override
123             public void run() {
124                 boolean streamClosed = false;
125                 boolean gotViews = false;
126                 for (StreamContext streamContext : mStreamContexts) {
127                     AudioStreamBase.StreamStatus status = streamContext.tester.getCurrentAudioStream().getStreamStatus();
128                     AudioStreamBase.DoubleStatistics latencyStatistics =
129                             streamContext.tester.getCurrentAudioStream().getLatencyStatistics();
130                     if (streamContext.configurationView != null) {
131                         // Handler runs this on the main UI thread.
132                         int framesPerBurst = streamContext.tester.getCurrentAudioStream().getFramesPerBurst();
133                         status.framesPerCallback = getFramesPerCallback();
134                         String msg = "";
135                         msg += "timestamp.latency = " + latencyStatistics.dump() + "\n";
136                         msg += status.dump(framesPerBurst);
137                         streamContext.configurationView.setStatusText(msg);
138                         updateStreamDisplay();
139                         gotViews = true;
140                     }
141 
142                     streamClosed = streamClosed || (status.state >= 12);
143                 }
144 
145                 if (streamClosed) {
146                     onStreamClosed();
147                 } else {
148                     // Repeat this runnable code block again.
149                     if (gotViews) {
150                         mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC);
151                     }
152                 }
153             }
154         };
155 
startStreamSniffer()156         private void startStreamSniffer() {
157             stopStreamSniffer();
158             mHandler = new Handler(Looper.getMainLooper());
159             // Start the initial runnable task by posting through the handler
160             mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC);
161         }
162 
stopStreamSniffer()163         private void stopStreamSniffer() {
164             if (mHandler != null) {
165                 mHandler.removeCallbacks(runnableCode);
166             }
167         }
168 
169     }
170 
setBackgroundEnabled(boolean enabled)171     public static void setBackgroundEnabled(boolean enabled) {
172         mBackgroundEnabled = enabled;
173     }
174 
isBackgroundEnabled()175     public static boolean isBackgroundEnabled() {
176         return mBackgroundEnabled;
177     }
178 
onStreamClosed()179     public void onStreamClosed() {
180     }
181 
inflateActivity()182     protected abstract void inflateActivity();
183 
updateStreamDisplay()184     void updateStreamDisplay() {
185     }
186 
187     @Override
onCreate(Bundle savedInstanceState)188     protected void onCreate(Bundle savedInstanceState) {
189         super.onCreate(savedInstanceState);
190         inflateActivity();
191         findAudioCommon();
192 
193         mBundleFromIntent = getIntent().getExtras();
194     }
195 
196     @Override
onNewIntent(Intent intent)197     public void onNewIntent(Intent intent) {
198         mBundleFromIntent = intent.getExtras();
199     }
200 
isTestConfiguredUsingBundle()201     public boolean isTestConfiguredUsingBundle() {
202         return mBundleFromIntent != null;
203     }
204 
hideSettingsViews()205     public void hideSettingsViews() {
206         for (StreamContext streamContext : mStreamContexts) {
207             if (streamContext.configurationView != null) {
208                 streamContext.configurationView.hideSettingsView();
209             }
210         }
211     }
212 
getActivityType()213     abstract int getActivityType();
214 
setSingleTestIndex(int testIndex)215     public void setSingleTestIndex(int testIndex) {
216         mSingleTestIndex = testIndex;
217     }
218 
getSingleTestIndex()219     public int getSingleTestIndex() {
220         return mSingleTestIndex;
221     }
222 
223     @Override
onStart()224     protected void onStart() {
225         super.onStart();
226         resetConfiguration();
227         setActivityType(getActivityType());
228     }
229 
resetConfiguration()230     protected void resetConfiguration() {
231     }
232 
233     @Override
onResume()234     public void onResume() {
235         super.onResume();
236         if (mBundleFromIntent != null) {
237             processBundleFromIntent();
238         }
239     }
240 
setVolumeFromIntent()241     private void setVolumeFromIntent() {
242         float normalizedVolume = IntentBasedTestSupport.getNormalizedVolumeFromBundle(mBundleFromIntent);
243         if (normalizedVolume >= 0.0) {
244             int streamType = IntentBasedTestSupport.getVolumeStreamTypeFromBundle(mBundleFromIntent);
245             AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
246             int maxVolume = audioManager.getStreamMaxVolume(streamType);
247             int requestedVolume = (int) (maxVolume * normalizedVolume);
248             audioManager.setStreamVolume(streamType, requestedVolume, 0);
249         }
250     }
251 
processBundleFromIntent()252     private void processBundleFromIntent() {
253         if (mTestRunningByIntent) {
254             return;
255         }
256 
257         // Delay the test start to avoid race conditions. See Oboe Issue #1533
258         mTestRunningByIntent = true;
259         Handler handler = new Handler(Looper.getMainLooper()); // UI thread
260         handler.postDelayed(new DelayedTestByIntentRunnable(),
261                 INTENT_TEST_DELAY_MILLIS); // Delay long enough to get past the onStop() call!
262 
263     }
264 
265     private class DelayedTestByIntentRunnable implements Runnable {
266         @Override
run()267         public void run() {
268             try {
269                 mResultFileName = mBundleFromIntent.getString(IntentBasedTestSupport.KEY_FILE_NAME);
270                 setVolumeFromIntent();
271                 startTestUsingBundle();
272             } catch( Exception e) {
273                 showErrorToast(e.getMessage());
274             }
275         }
276     }
277 
startTestUsingBundle()278     public void startTestUsingBundle() {
279     }
280 
281     @Override
onPause()282     protected void onPause() {
283         super.onPause();
284     }
285 
286     @Override
onStop()287     protected void onStop() {
288         if (!isBackgroundEnabled()) {
289             Log.i(TAG, "onStop() called so stop the test =========================");
290             onStopTest();
291         }
292         super.onStop();
293     }
294 
295     @Override
onDestroy()296     protected void onDestroy() {
297         if (isBackgroundEnabled()) {
298             Log.i(TAG, "onDestroy() called so stop the test =========================");
299             onStopTest();
300         }
301         mAudioState = AUDIO_STATE_CLOSED;
302         super.onDestroy();
303     }
304 
updateEnabledWidgets()305     protected void updateEnabledWidgets() {
306         if (mOpenButton != null) {
307             mOpenButton.setBackgroundColor(mAudioState == AUDIO_STATE_OPEN ? COLOR_ACTIVE : COLOR_IDLE);
308             mStartButton.setBackgroundColor(mAudioState == AUDIO_STATE_STARTED ? COLOR_ACTIVE : COLOR_IDLE);
309             mPauseButton.setBackgroundColor(mAudioState == AUDIO_STATE_PAUSED ? COLOR_ACTIVE : COLOR_IDLE);
310             mStopButton.setBackgroundColor(mAudioState == AUDIO_STATE_STOPPED ? COLOR_ACTIVE : COLOR_IDLE);
311             mCloseButton.setBackgroundColor(mAudioState == AUDIO_STATE_CLOSED ? COLOR_ACTIVE : COLOR_IDLE);
312         }
313         setConfigViewsEnabled(mAudioState == AUDIO_STATE_CLOSED);
314     }
315 
setConfigViewsEnabled(boolean b)316     private void setConfigViewsEnabled(boolean b) {
317         for (StreamContext streamContext : mStreamContexts) {
318             if (streamContext.configurationView != null) {
319                 streamContext.configurationView.setChildrenEnabled(b);
320             }
321         }
322     }
323 
applyConfigurationViewsToModels()324     private void applyConfigurationViewsToModels() {
325         for (StreamContext streamContext : mStreamContexts) {
326             if (streamContext.configurationView != null) {
327                 streamContext.configurationView.applyToModel(streamContext.tester.requestedConfiguration);
328             }
329         }
330     }
331 
isOutput()332     abstract boolean isOutput();
333 
clearStreamContexts()334     public void clearStreamContexts() {
335         mStreamContexts.clear();
336     }
337 
addOutputStreamContext()338     public StreamContext addOutputStreamContext() {
339         StreamContext streamContext = new StreamContext();
340         streamContext.tester = AudioOutputTester.getInstance();
341         streamContext.configurationView = (StreamConfigurationView)
342                 findViewById(R.id.outputStreamConfiguration);
343         if (streamContext.configurationView == null) {
344             streamContext.configurationView = (StreamConfigurationView)
345                     findViewById(R.id.streamConfiguration);
346         }
347         if (streamContext.configurationView != null) {
348             streamContext.configurationView.setOutput(true);
349         }
350         mStreamContexts.add(streamContext);
351         return streamContext;
352     }
353 
addAudioOutputTester()354     public AudioOutputTester addAudioOutputTester() {
355         StreamContext streamContext = addOutputStreamContext();
356         return (AudioOutputTester) streamContext.tester;
357     }
358 
addInputStreamContext()359     public StreamContext addInputStreamContext() {
360         StreamContext streamContext = new StreamContext();
361         streamContext.tester = AudioInputTester.getInstance();
362         streamContext.configurationView = (StreamConfigurationView)
363                 findViewById(R.id.inputStreamConfiguration);
364         if (streamContext.configurationView == null) {
365             streamContext.configurationView = (StreamConfigurationView)
366                     findViewById(R.id.streamConfiguration);
367         }
368         if (streamContext.configurationView != null) {
369             streamContext.configurationView.setOutput(false);
370         }
371         streamContext.tester = AudioInputTester.getInstance();
372         mStreamContexts.add(streamContext);
373         return streamContext;
374     }
375 
addAudioInputTester()376     public AudioInputTester addAudioInputTester() {
377         StreamContext streamContext = addInputStreamContext();
378         return (AudioInputTester) streamContext.tester;
379     }
380 
updateStreamConfigurationViews()381     void updateStreamConfigurationViews() {
382         for (StreamContext streamContext : mStreamContexts) {
383             if (streamContext.configurationView != null) {
384                 streamContext.configurationView.updateDisplay(streamContext.tester.actualConfiguration);
385             }
386         }
387     }
388 
getFirstInputStreamContext()389     StreamContext getFirstInputStreamContext() {
390         for (StreamContext streamContext : mStreamContexts) {
391             if (streamContext.isInput())
392                 return streamContext;
393         }
394         return null;
395     }
396 
getFirstOutputStreamContext()397     StreamContext getFirstOutputStreamContext() {
398         for (StreamContext streamContext : mStreamContexts) {
399             if (!streamContext.isInput())
400                 return streamContext;
401         }
402         return null;
403     }
404 
findAudioCommon()405     protected void findAudioCommon() {
406         mOpenButton = (Button) findViewById(R.id.button_open);
407         if (mOpenButton != null) {
408             mStartButton = (Button) findViewById(R.id.button_start);
409             mPauseButton = (Button) findViewById(R.id.button_pause);
410             mStopButton = (Button) findViewById(R.id.button_stop);
411             mCloseButton = (Button) findViewById(R.id.button_close);
412         }
413         mStreamContexts = new ArrayList<StreamContext>();
414 
415         mCallbackReturnStopBox = (CheckBox) findViewById(R.id.callbackReturnStop);
416         if (mCallbackReturnStopBox != null) {
417             mCallbackReturnStopBox.setOnClickListener(new View.OnClickListener() {
418                 @Override
419                 public void onClick(View v) {
420                     OboeAudioStream.setCallbackReturnStop(mCallbackReturnStopBox.isChecked());
421                 }
422             });
423         }
424         OboeAudioStream.setCallbackReturnStop(false);
425 
426         mStreamSniffer = new MyStreamSniffer();
427     }
428 
updateNativeAudioParameters()429     private void updateNativeAudioParameters() {
430         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
431             AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
432             String text = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
433             int audioManagerSampleRate = Integer.parseInt(text);
434             text = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
435             int audioManagerFramesPerBurst = Integer.parseInt(text);
436             setDefaultAudioValues(audioManagerSampleRate, audioManagerFramesPerBurst);
437         }
438     }
439 
showErrorToast(String message)440     protected void showErrorToast(String message) {
441         String text = "Error: " + message;
442         Log.e(TAG, text);
443         showToast(text);
444     }
445 
showToast(final String message)446     protected void showToast(final String message) {
447         runOnUiThread(new Runnable() {
448             @Override
449             public void run() {
450                 Toast.makeText(TestAudioActivity.this,
451                         message,
452                         Toast.LENGTH_SHORT).show();
453             }
454         });
455     }
456 
openAudio(View view)457     public void openAudio(View view) {
458         try {
459             openAudio();
460         } catch (Exception e) {
461             showErrorToast(e.getMessage());
462         }
463     }
464 
startAudio(View view)465     public void startAudio(View view) {
466         Log.i(TAG, "startAudio() called =======================================");
467         try {
468             startAudio();
469         } catch (Exception e) {
470             showErrorToast(e.getMessage());
471         }
472         keepScreenOn(true);
473     }
474 
keepScreenOn(boolean on)475     protected void keepScreenOn(boolean on) {
476         if (on) {
477             getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
478         } else {
479             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
480         }
481     }
482 
stopAudio(View view)483     public void stopAudio(View view) {
484         stopAudio();
485         keepScreenOn(false);
486     }
487 
pauseAudio(View view)488     public void pauseAudio(View view) {
489         pauseAudio();
490         keepScreenOn(false);
491     }
492 
closeAudio(View view)493     public void closeAudio(View view) {
494         closeAudio();
495     }
496 
getSampleRate()497     public int getSampleRate() {
498         return mSampleRate;
499     }
500 
openAudio()501     public void openAudio() throws IOException {
502         closeAudio();
503 
504         updateNativeAudioParameters();
505 
506         if (!isTestConfiguredUsingBundle()) {
507             applyConfigurationViewsToModels();
508         }
509 
510         int sampleRate = 0;
511 
512         // Open output streams then open input streams.
513         // This is so that the capacity of input stream can be expanded to
514         // match the burst size of the output for full duplex.
515         for (StreamContext streamContext : mStreamContexts) {
516             if (!streamContext.isInput()) {
517                 openStreamContext(streamContext);
518                 int streamSampleRate = streamContext.tester.actualConfiguration.getSampleRate();
519                 if (sampleRate == 0) {
520                     sampleRate = streamSampleRate;
521                 }
522             }
523         }
524         for (StreamContext streamContext : mStreamContexts) {
525             if (streamContext.isInput()) {
526                 if (sampleRate != 0) {
527                     streamContext.tester.requestedConfiguration.setSampleRate(sampleRate);
528                 }
529                 openStreamContext(streamContext);
530             }
531         }
532         updateEnabledWidgets();
533         mStreamSniffer.startStreamSniffer();
534     }
535 
536     /**
537      * @param deviceId
538      * @return true if the device is TYPE_BLUETOOTH_SCO
539      */
isScoDevice(int deviceId)540     boolean isScoDevice(int deviceId) {
541         if (deviceId == 0) return false; // Unspecified
542         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
543         final AudioDeviceInfo[] devices = audioManager.getDevices(
544                 AudioManager.GET_DEVICES_INPUTS | AudioManager.GET_DEVICES_OUTPUTS);
545         for (AudioDeviceInfo device : devices) {
546             if (device.getId() == deviceId) {
547                 return device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
548             }
549         }
550         return false;
551     }
552 
openStreamContext(StreamContext streamContext)553     private void openStreamContext(StreamContext streamContext) throws IOException {
554         StreamConfiguration requestedConfig = streamContext.tester.requestedConfiguration;
555         StreamConfiguration actualConfig = streamContext.tester.actualConfiguration;
556 
557         streamContext.tester.open(); // OPEN the stream
558 
559         mSampleRate = actualConfig.getSampleRate();
560         mAudioState = AUDIO_STATE_OPEN;
561         int sessionId = actualConfig.getSessionId();
562         if (streamContext.configurationView != null) {
563             if (sessionId > 0) {
564                 try {
565                     streamContext.configurationView.setupEffects(sessionId);
566                 } catch (Exception e) {
567                     showErrorToast(e.getMessage());
568                 }
569             }
570             streamContext.configurationView.updateDisplay(streamContext.tester.actualConfiguration);
571         }
572     }
573 
574     // Native methods
startNative()575     private native int startNative();
576 
pauseNative()577     private native int pauseNative();
578 
stopNative()579     private native int stopNative();
580 
setActivityType(int activityType)581     protected native void setActivityType(int activityType);
582 
getFramesPerCallback()583     private native int getFramesPerCallback();
584 
setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst)585     private static native void setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst);
586 
startAudio()587     public void startAudio() throws IOException {
588         Log.i(TAG, "startAudio() called =========================");
589         int result = startNative();
590         if (result < 0) {
591             showErrorToast("Start failed with " + result);
592             throw new IOException("startNative returned " + result);
593         } else {
594             for (StreamContext streamContext : mStreamContexts) {
595                 StreamConfigurationView configView = streamContext.configurationView;
596                 if (configView != null) {
597                     configView.updateDisplay(streamContext.tester.actualConfiguration);
598                 }
599             }
600             mAudioState = AUDIO_STATE_STARTED;
601             updateEnabledWidgets();
602         }
603     }
604 
toastPauseError(int result)605     protected void toastPauseError(int result) {
606         showErrorToast("Pause failed with " + result);
607     }
608 
pauseAudio()609     public void pauseAudio() {
610         int result = pauseNative();
611         if (result < 0) {
612             toastPauseError(result);
613         } else {
614             mAudioState = AUDIO_STATE_PAUSED;
615             updateEnabledWidgets();
616         }
617     }
618 
stopAudio()619     public void stopAudio() {
620         int result = stopNative();
621         if (result < 0) {
622             showErrorToast("Stop failed with " + result);
623         } else {
624             mAudioState = AUDIO_STATE_STOPPED;
625             updateEnabledWidgets();
626         }
627     }
628 
runTest()629     public void runTest() {
630     }
631 
saveIntentLog()632     public void saveIntentLog() {
633     }
634 
635     // This should only be called from UI events such as onStop or a button press.
onStopTest()636     public void onStopTest() {
637         stopTest();
638     }
639 
stopTest()640     public void stopTest() {
641         stopAudio();
642         closeAudio();
643     }
644 
stopAudioQuiet()645     public void stopAudioQuiet() {
646         stopNative();
647         mAudioState = AUDIO_STATE_STOPPED;
648         updateEnabledWidgets();
649     }
650 
651     // Make synchronized so we don't close from two streams at the same time.
closeAudio()652     public synchronized void closeAudio() {
653         if (mAudioState >= AUDIO_STATE_CLOSING) {
654             Log.d(TAG, "closeAudio() already closing");
655             return;
656         }
657         mAudioState = AUDIO_STATE_CLOSING;
658 
659         mStreamSniffer.stopStreamSniffer();
660         // Close output streams first because legacy callbacks may still be active
661         // and an output stream may be calling the input stream.
662         for (StreamContext streamContext : mStreamContexts) {
663             if (!streamContext.isInput()) {
664                 streamContext.tester.close();
665             }
666         }
667         for (StreamContext streamContext : mStreamContexts) {
668             if (streamContext.isInput()) {
669                 streamContext.tester.close();
670             }
671         }
672 
673         mAudioState = AUDIO_STATE_CLOSED;
674         updateEnabledWidgets();
675     }
676 
startBluetoothSco()677     void startBluetoothSco() {
678         AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
679         myAudioMgr.startBluetoothSco();
680     }
681 
stopBluetoothSco()682     void stopBluetoothSco() {
683         AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
684         myAudioMgr.stopBluetoothSco();
685     }
686 
687     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)688     public void onRequestPermissionsResult(int requestCode,
689                                            String[] permissions,
690                                            int[] grantResults) {
691 
692         if (MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE != requestCode) {
693             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
694             return;
695         }
696         // If request is cancelled, the result arrays are empty.
697         if (grantResults.length > 0
698                 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
699             writeTestResult(mTestResults);
700         } else {
701             showToast("Writing external storage needed for test results.");
702         }
703     }
704 
705     @NonNull
getCommonTestReport()706     protected String getCommonTestReport() {
707         StringBuffer report = new StringBuffer();
708         // Add some extra information for the remote tester.
709         report.append("build.fingerprint = " + Build.FINGERPRINT + "\n");
710         try {
711             PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
712             report.append(String.format("test.version = %s\n", pinfo.versionName));
713             report.append(String.format("test.version.code = %d\n", pinfo.versionCode));
714         } catch (PackageManager.NameNotFoundException e) {
715         }
716         report.append("time.millis = " + System.currentTimeMillis() + "\n");
717 
718         if (mStreamContexts.size() == 0) {
719             report.append("ERROR: no active streams" + "\n");
720         } else {
721             StreamContext streamContext = mStreamContexts.get(0);
722             AudioStreamTester streamTester = streamContext.tester;
723             report.append(streamTester.actualConfiguration.dump());
724             AudioStreamBase.StreamStatus status = streamTester.getCurrentAudioStream().getStreamStatus();
725             AudioStreamBase.DoubleStatistics latencyStatistics =
726                     streamTester.getCurrentAudioStream().getLatencyStatistics();
727             int framesPerBurst = streamTester.getCurrentAudioStream().getFramesPerBurst();
728             status.framesPerCallback = getFramesPerCallback();
729             report.append("timestamp.latency = " + latencyStatistics.dump() + "\n");
730             report.append(status.dump(framesPerBurst));
731         }
732 
733         return report.toString();
734     }
735 
writeTestResultIfPermitted(String resultString)736     void writeTestResultIfPermitted(String resultString) {
737         // Here, thisActivity is the current activity
738         if (ContextCompat.checkSelfPermission(this,
739                 Manifest.permission.WRITE_EXTERNAL_STORAGE)
740                 != PackageManager.PERMISSION_GRANTED) {
741             mTestResults = resultString;
742             ActivityCompat.requestPermissions(this,
743                     new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
744                     MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE);
745         } else {
746             // Permission has already been granted
747             writeTestResult(resultString);
748         }
749     }
750 
maybeWriteTestResult(String resultString)751     void maybeWriteTestResult(String resultString) {
752         if (mResultFileName != null) {
753             writeTestResultIfPermitted(resultString);
754         };
755     }
756 
757     // Run this in a background thread.
writeTestResult(String resultString)758     void writeTestResult(String resultString) {
759         File resultFile = new File(mResultFileName);
760         Writer writer = null;
761         try {
762             writer = new OutputStreamWriter(new FileOutputStream(resultFile));
763             writer.write(resultString);
764         } catch (
765                 IOException e) {
766             e.printStackTrace();
767             showErrorToast(" writing result file. " + e.getMessage());
768         } finally {
769             if (writer != null) {
770                 try {
771                     writer.close();
772                 } catch (IOException e) {
773                     e.printStackTrace();
774                 }
775             }
776         }
777 
778         mResultFileName = null;
779     }
780 }
781