1 /* 2 * Copyright 2018 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.google.sample.oboe.manualtest; 18 19 import android.Manifest; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageManager; 22 import android.os.Build; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.support.annotation.NonNull; 27 import android.support.v4.app.ActivityCompat; 28 import android.support.v4.content.ContextCompat; 29 import android.util.Log; 30 import android.view.View; 31 import android.widget.Button; 32 import android.widget.TextView; 33 import android.widget.Toast; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.OutputStreamWriter; 39 import java.io.Writer; 40 41 /** 42 * Activity to measure latency on a full duplex stream. 43 */ 44 public class AnalyzerActivity extends TestInputActivity { 45 46 private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 1001; 47 48 protected static final String KEY_FILE_NAME = "file"; 49 protected static final String KEY_BUFFER_BURSTS = "buffer_bursts"; 50 51 public static final String VALUE_UNSPECIFIED = "unspecified"; 52 public static final String KEY_IN_API = "in_api"; 53 public static final String KEY_OUT_API = "out_api"; 54 public static final String VALUE_API_AAUDIO = "aaudio"; 55 public static final String VALUE_API_OPENSLES = "opensles"; 56 57 AudioOutputTester mAudioOutTester; 58 protected BufferSizeView mBufferSizeView; 59 protected String mResultFileName; 60 private String mTestResults; 61 62 // Note that these string must match the enum result_code in LatencyAnalyzer.h resultCodeToString(int resultCode)63 String resultCodeToString(int resultCode) { 64 switch (resultCode) { 65 case 0: 66 return "OK"; 67 case -99: 68 return "ERROR_NOISY"; 69 case -98: 70 return "ERROR_VOLUME_TOO_LOW"; 71 case -97: 72 return "ERROR_VOLUME_TOO_HIGH"; 73 case -96: 74 return "ERROR_CONFIDENCE"; 75 case -95: 76 return "ERROR_INVALID_STATE"; 77 case -94: 78 return "ERROR_GLITCHES"; 79 case -93: 80 return "ERROR_NO_LOCK"; 81 default: 82 return "UNKNOWN"; 83 } 84 } 85 getAnalyzerState()86 public native int getAnalyzerState(); isAnalyzerDone()87 public native boolean isAnalyzerDone(); getMeasuredResult()88 public native int getMeasuredResult(); getResetCount()89 public native int getResetCount(); 90 91 @NonNull getCommonTestReport()92 protected String getCommonTestReport() { 93 StringBuffer report = new StringBuffer(); 94 // Add some extra information for the remote tester. 95 report.append("build.fingerprint = " + Build.FINGERPRINT + "\n"); 96 try { 97 PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0); 98 report.append(String.format("test.version = %s\n", pinfo.versionName)); 99 report.append(String.format("test.version.code = %d\n", pinfo.versionCode)); 100 } catch (PackageManager.NameNotFoundException e) { 101 } 102 report.append("time.millis = " + System.currentTimeMillis() + "\n"); 103 104 // INPUT 105 report.append(mAudioInputTester.actualConfiguration.dump()); 106 AudioStreamBase inStream = mAudioInputTester.getCurrentAudioStream(); 107 report.append(String.format("in.burst.frames = %d\n", inStream.getFramesPerBurst())); 108 report.append(String.format("in.xruns = %d\n", inStream.getXRunCount())); 109 110 // OUTPUT 111 report.append(mAudioOutTester.actualConfiguration.dump()); 112 AudioStreamBase outStream = mAudioOutTester.getCurrentAudioStream(); 113 report.append(String.format("out.burst.frames = %d\n", outStream.getFramesPerBurst())); 114 int bufferSize = outStream.getBufferSizeInFrames(); 115 report.append(String.format("out.buffer.size.frames = %d\n", bufferSize)); 116 int bufferCapacity = outStream.getBufferCapacityInFrames(); 117 report.append(String.format("out.buffer.capacity.frames = %d\n", bufferCapacity)); 118 report.append(String.format("out.xruns = %d\n", outStream.getXRunCount())); 119 120 return report.toString(); 121 } 122 123 @Override onCreate(Bundle savedInstanceState)124 protected void onCreate(Bundle savedInstanceState) { 125 super.onCreate(savedInstanceState); 126 mAudioOutTester = addAudioOutputTester(); 127 mBufferSizeView = (BufferSizeView) findViewById(R.id.buffer_size_view); 128 if (mBufferSizeView != null) { 129 mBufferSizeView.setAudioOutTester(mAudioOutTester); 130 } 131 } 132 133 @Override resetConfiguration()134 protected void resetConfiguration() { 135 super.resetConfiguration(); 136 mAudioOutTester.reset(); 137 138 StreamContext streamContext = getFirstInputStreamContext(); 139 if (streamContext != null) { 140 if (streamContext.configurationView != null) { 141 streamContext.configurationView.setFormat(StreamConfiguration.AUDIO_FORMAT_PCM_FLOAT); 142 streamContext.configurationView.setFormatConversionAllowed(true); 143 } 144 } 145 streamContext = getFirstOutputStreamContext(); 146 if (streamContext != null) { 147 if (streamContext.configurationView != null) { 148 streamContext.configurationView.setFormat(StreamConfiguration.AUDIO_FORMAT_PCM_FLOAT); 149 streamContext.configurationView.setFormatConversionAllowed(true); 150 } 151 } 152 } 153 startAudio()154 public void startAudio() { 155 if (mBufferSizeView != null && mBufferSizeView.isEnabled()) { 156 mBufferSizeView.updateBufferSize(); 157 } 158 super.startAudio(); 159 } 160 onStreamClosed()161 public void onStreamClosed() { 162 Toast.makeText(getApplicationContext(), 163 "Stream was closed or disconnected!", 164 Toast.LENGTH_SHORT) 165 .show(); 166 stopAudioTest(); 167 } 168 stopAudioTest()169 public void stopAudioTest() { 170 } 171 getApiFromText(String text)172 private int getApiFromText(String text) { 173 if (VALUE_API_AAUDIO.equals(text)) { 174 return StreamConfiguration.NATIVE_API_AAUDIO; 175 } else if (VALUE_API_OPENSLES.equals(text)) { 176 return StreamConfiguration.NATIVE_API_OPENSLES; 177 } else { 178 return StreamConfiguration.NATIVE_API_UNSPECIFIED; 179 } 180 } 181 configureStreamsFromBundleForApi(Bundle bundle)182 void configureStreamsFromBundleForApi(Bundle bundle) { 183 // Configure settings 184 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 185 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 186 187 // OpenSL ES or AAudio API 188 String text = bundle.getString(KEY_IN_API, VALUE_UNSPECIFIED); 189 int audioApi = getApiFromText(text); 190 requestedInConfig.setNativeApi(audioApi); 191 text = bundle.getString(KEY_OUT_API, VALUE_UNSPECIFIED); 192 audioApi = getApiFromText(text); 193 requestedOutConfig.setNativeApi(audioApi); 194 } 195 writeTestResultIfPermitted(String resultString)196 void writeTestResultIfPermitted(String resultString) { 197 // Here, thisActivity is the current activity 198 if (ContextCompat.checkSelfPermission(this, 199 Manifest.permission.WRITE_EXTERNAL_STORAGE) 200 != PackageManager.PERMISSION_GRANTED) { 201 mTestResults = resultString; 202 ActivityCompat.requestPermissions(this, 203 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 204 MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE); 205 } else { 206 // Permission has already been granted 207 writeTestResult(resultString); 208 } 209 } 210 maybeWriteTestResult(String resultString)211 void maybeWriteTestResult(String resultString) { 212 if (mResultFileName == null) return; 213 writeTestResultIfPermitted(resultString); 214 } 215 216 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)217 public void onRequestPermissionsResult(int requestCode, 218 String[] permissions, 219 int[] grantResults) { 220 switch (requestCode) { 221 case MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE: { 222 // If request is cancelled, the result arrays are empty. 223 if (grantResults.length > 0 224 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 225 writeTestResult(mTestResults); 226 } else { 227 showToast("Writing external storage needed for test results."); 228 } 229 return; 230 } 231 } 232 } 233 writeTestInBackground(final String resultString)234 private void writeTestInBackground(final String resultString) { 235 new Thread() { 236 public void run() { 237 writeTestResult(resultString); 238 } 239 }.start(); 240 } 241 242 // Run this in a background thread. writeTestResult(String resultString)243 private void writeTestResult(String resultString) { 244 File resultFile = new File(mResultFileName); 245 Writer writer = null; 246 try { 247 writer = new OutputStreamWriter(new FileOutputStream(resultFile)); 248 writer.write(resultString); 249 } catch ( 250 IOException e) { 251 e.printStackTrace(); 252 showErrorToast(" writing result file. " + e.getMessage()); 253 } finally { 254 if (writer != null) { 255 try { 256 writer.close(); 257 } catch (IOException e) { 258 e.printStackTrace(); 259 } 260 } 261 } 262 263 mResultFileName = null; 264 } 265 266 267 } 268