1 /* 2 * Copyright (C) 2011 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.android.cts.verifier.audio; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 22 import android.content.Context; 23 import android.media.AudioFormat; 24 import android.media.AudioManager; 25 import android.media.AudioTrack; 26 import android.os.AsyncTask; 27 import android.os.Bundle; 28 import android.text.method.ScrollingMovementMethod; 29 import android.util.Log; 30 import android.view.Gravity; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.widget.Button; 34 import android.widget.LinearLayout; 35 import android.widget.LinearLayout.LayoutParams; 36 import android.widget.PopupWindow; 37 import android.widget.TextView; 38 import java.util.Arrays; 39 40 import com.androidplot.xy.PointLabelFormatter; 41 import com.androidplot.xy.LineAndPointFormatter; 42 import com.androidplot.xy.SimpleXYSeries; 43 import com.androidplot.xy.XYPlot; 44 import com.androidplot.xy.XYSeries; 45 import com.androidplot.xy.XYStepMode; 46 47 import com.android.compatibility.common.util.CddTest; 48 49 @CddTest(requirement = "7.8.3/C-1-1,C-1-2,C-2-1") 50 public class HifiUltrasoundSpeakerTestActivity extends PassFailButtons.Activity { 51 52 public enum Status { 53 START, RECORDING, DONE, PLAYER 54 } 55 56 private static final String TAG = "HifiUltrasoundTestActivity"; 57 58 private Status status = Status.START; 59 private boolean onPlotScreen = false; 60 private boolean onInstruScreen = false; 61 private TextView info; 62 private Button playerButton; 63 private Button recorderButton; 64 private AudioTrack audioTrack; 65 private LayoutInflater layoutInflater; 66 private View popupView; 67 private View instruView; 68 private PopupWindow popupWindow; 69 private PopupWindow instruWindow; 70 private boolean micSupport = true; 71 private boolean spkrSupport = true; 72 73 @Override onBackPressed()74 public void onBackPressed () { 75 if (onPlotScreen) { 76 popupWindow.dismiss(); 77 onPlotScreen = false; 78 recorderButton.setEnabled(true); 79 } else if (onInstruScreen) { 80 instruWindow.dismiss(); 81 onInstruScreen = false; 82 if (status == Status.PLAYER) { 83 playerButton.setEnabled(spkrSupport); 84 } else { 85 recorderButton.setEnabled(micSupport); 86 } 87 if (status == Status.PLAYER) { 88 getPassButton().setEnabled(true); 89 } 90 } else { 91 super.onBackPressed(); 92 } 93 } 94 95 @Override onPause()96 public void onPause() { 97 super.onPause(); 98 if (audioTrack != null) { 99 audioTrack.stop(); 100 audioTrack.release(); 101 audioTrack = null; 102 } 103 } 104 getBoolPropValue(final String value)105 boolean getBoolPropValue(final String value) { 106 if (value == null) { 107 return false; 108 } 109 110 return !value.equalsIgnoreCase(getResources().getString( 111 R.string.hifi_ultrasound_test_default_false_string)); 112 } 113 114 @Override onCreate(Bundle savedInstanceState)115 protected void onCreate(Bundle savedInstanceState) { 116 super.onCreate(savedInstanceState); 117 setContentView(R.layout.hifi_ultrasound); 118 setInfoResources(R.string.hifi_ultrasound_speaker_test, 119 R.string.hifi_ultrasound_speaker_test_info, -1); 120 setPassFailButtonClickListeners(); 121 getPassButton().setEnabled(false); 122 123 info = (TextView) findViewById(R.id.info_text); 124 info.setMovementMethod(new ScrollingMovementMethod()); 125 info.setText(R.string.hifi_ultrasound_speaker_test_instruction1); 126 127 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 128 micSupport = getBoolPropValue(audioManager.getProperty( 129 AudioManager.PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND)); 130 spkrSupport = getBoolPropValue(audioManager.getProperty( 131 AudioManager.PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND)); 132 Log.d(TAG, "PROPERTY_SUPPORT_MIC_NEAR_ULTRASOUND = " + micSupport); 133 Log.d(TAG, "PROPERTY_SUPPORT_SPEAKER_NEAR_ULTRASOUND = " + spkrSupport); 134 135 if (!micSupport) { 136 info.append(getResources().getString(R.string.hifi_ultrasound_speaker_test_mic_no_support)); 137 } 138 if (!spkrSupport) { 139 getPassButton().setEnabled(true); 140 info.append(getResources().getString(R.string.hifi_ultrasound_speaker_test_spkr_no_support)); 141 } 142 143 layoutInflater = (LayoutInflater) getBaseContext().getSystemService( 144 LAYOUT_INFLATER_SERVICE); 145 popupView = layoutInflater.inflate(R.layout.hifi_ultrasound_popup, null); 146 popupWindow = new PopupWindow( 147 popupView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 148 instruView = layoutInflater.inflate(R.layout.hifi_ultrasound_popup_instru, null); 149 instruWindow = new PopupWindow( 150 instruView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 151 152 final AudioRecordHelper audioRecorder = AudioRecordHelper.getInstance(); 153 final int recordRate = audioRecorder.getSampleRate(); 154 155 recorderButton = (Button) findViewById(R.id.recorder_button); 156 recorderButton.setEnabled(micSupport); 157 recorderButton.setOnClickListener(new View.OnClickListener() { 158 private WavAnalyzerTask wavAnalyzerTask = null; 159 private void stopRecording() { 160 audioRecorder.stop(); 161 wavAnalyzerTask = new WavAnalyzerTask(audioRecorder.getByte()); 162 wavAnalyzerTask.execute(); 163 status = Status.DONE; 164 } 165 @Override 166 public void onClick(View v) { 167 switch (status) { 168 case START: 169 info.append("Recording at " + recordRate + "Hz using "); 170 final int source = audioRecorder.getAudioSource(); 171 switch (source) { 172 case 1: 173 info.append("MIC"); 174 break; 175 case 6: 176 info.append("VOICE_RECOGNITION"); 177 break; 178 default: 179 info.append("UNEXPECTED " + source); 180 break; 181 } 182 info.append("\n"); 183 status = Status.RECORDING; 184 playerButton.setEnabled(false); 185 recorderButton.setEnabled(false); 186 audioRecorder.start(); 187 188 final View finalV = v; 189 new Thread() { 190 @Override 191 public void run() { 192 Double recordingDuration_millis = new Double(1000 * (2.5 193 + Common.PREFIX_LENGTH_S 194 + Common.PAUSE_BEFORE_PREFIX_DURATION_S 195 + Common.PAUSE_AFTER_PREFIX_DURATION_S 196 + Common.PIP_NUM * (Common.PIP_DURATION_S + Common.PAUSE_DURATION_S) 197 * Common.REPETITIONS)); 198 Log.d(TAG, "Recording for " + recordingDuration_millis + "ms"); 199 try { 200 Thread.sleep(recordingDuration_millis.intValue()); 201 } catch (InterruptedException e) { 202 throw new RuntimeException(e); 203 } 204 runOnUiThread(new Runnable() { 205 @Override 206 public void run() { 207 stopRecording(); 208 } 209 }); 210 } 211 }.start(); 212 213 break; 214 215 case DONE: 216 plotResponse(wavAnalyzerTask); 217 break; 218 219 default: break; 220 } 221 } 222 }); 223 224 playerButton = (Button) findViewById(R.id.player_button); 225 playerButton.setEnabled(spkrSupport); 226 playerButton.setOnClickListener(new View.OnClickListener() { 227 @Override 228 public void onClick(View v) { 229 recorderButton.setEnabled(false); 230 status = Status.PLAYER; 231 play(); 232 233 Button okButton = (Button)instruView.findViewById(R.id.ok); 234 okButton.setOnClickListener(new Button.OnClickListener() { 235 @Override 236 public void onClick(View v) { 237 instruWindow.dismiss(); 238 onInstruScreen = false; 239 if (status == Status.PLAYER) { 240 playerButton.setEnabled(spkrSupport); 241 } else { 242 recorderButton.setEnabled(micSupport); 243 } 244 getPassButton().setEnabled(true); 245 } 246 }); 247 TextView instruction = (TextView)instruView.findViewById(R.id.instru); 248 instruction.setText(R.string.hifi_ultrasound_speaker_test_test_side); 249 instruWindow.showAtLocation(info, Gravity.CENTER, 0, 0); 250 recorderButton.setEnabled(false); 251 playerButton.setEnabled(false); 252 onInstruScreen = true; 253 } 254 }); 255 } 256 plotResponse(WavAnalyzerTask wavAnalyzerTask)257 private void plotResponse(WavAnalyzerTask wavAnalyzerTask) { 258 Button dismissButton = (Button)popupView.findViewById(R.id.dismiss); 259 dismissButton.setOnClickListener(new Button.OnClickListener(){ 260 @Override 261 public void onClick(View v) { 262 popupWindow.dismiss(); 263 onPlotScreen = false; 264 recorderButton.setEnabled(true); 265 }}); 266 popupWindow.showAtLocation(info, Gravity.CENTER, 0, 0); 267 onPlotScreen = true; 268 269 recorderButton.setEnabled(false); 270 271 XYPlot plot = (XYPlot) popupView.findViewById(R.id.responseChart); 272 plot.setDomainStep(XYStepMode.INCREMENT_BY_VAL, 2000); 273 274 Double[] frequencies = new Double[Common.PIP_NUM]; 275 for (int i = 0; i < Common.PIP_NUM; i++) { 276 frequencies[i] = new Double(Common.FREQUENCIES_ORIGINAL[i]); 277 } 278 279 if (wavAnalyzerTask != null) { 280 281 double[][] power = wavAnalyzerTask.getPower(); 282 for(int i = 0; i < Common.REPETITIONS; i++) { 283 Double[] powerWrap = new Double[Common.PIP_NUM]; 284 for (int j = 0; j < Common.PIP_NUM; j++) { 285 powerWrap[j] = new Double(10 * Math.log10(power[j][i])); 286 } 287 XYSeries series = new SimpleXYSeries( 288 Arrays.asList(frequencies), 289 Arrays.asList(powerWrap), 290 ""); 291 LineAndPointFormatter seriesFormat = new LineAndPointFormatter(); 292 seriesFormat.setPointLabelFormatter(new PointLabelFormatter()); 293 seriesFormat.configure(getApplicationContext(), 294 R.xml.ultrasound_line_formatter_trials); 295 plot.addSeries(series, seriesFormat); 296 } 297 298 double[] noiseDB = wavAnalyzerTask.getNoiseDB(); 299 Double[] noiseDBWrap = new Double[Common.PIP_NUM]; 300 for (int i = 0; i < Common.PIP_NUM; i++) { 301 noiseDBWrap[i] = new Double(noiseDB[i]); 302 } 303 304 XYSeries noiseSeries = new SimpleXYSeries( 305 Arrays.asList(frequencies), 306 Arrays.asList(noiseDBWrap), 307 "background noise"); 308 LineAndPointFormatter noiseSeriesFormat = new LineAndPointFormatter(); 309 noiseSeriesFormat.setPointLabelFormatter(new PointLabelFormatter()); 310 noiseSeriesFormat.configure(getApplicationContext(), 311 R.xml.ultrasound_line_formatter_noise); 312 plot.addSeries(noiseSeries, noiseSeriesFormat); 313 314 double[] dB = wavAnalyzerTask.getDB(); 315 Double[] dBWrap = new Double[Common.PIP_NUM]; 316 for (int i = 0; i < Common.PIP_NUM; i++) { 317 dBWrap[i] = new Double(dB[i]); 318 } 319 320 XYSeries series = new SimpleXYSeries( 321 Arrays.asList(frequencies), 322 Arrays.asList(dBWrap), 323 "median"); 324 LineAndPointFormatter seriesFormat = new LineAndPointFormatter(); 325 seriesFormat.setPointLabelFormatter(new PointLabelFormatter()); 326 seriesFormat.configure(getApplicationContext(), 327 R.xml.ultrasound_line_formatter_median); 328 plot.addSeries(series, seriesFormat); 329 330 Double[] passX = new Double[] {Common.MIN_FREQUENCY_HZ, Common.MAX_FREQUENCY_HZ}; 331 Double[] passY = new Double[] {wavAnalyzerTask.getThreshold(), wavAnalyzerTask.getThreshold()}; 332 XYSeries passSeries = new SimpleXYSeries( 333 Arrays.asList(passX), Arrays.asList(passY), "passing"); 334 LineAndPointFormatter passSeriesFormat = new LineAndPointFormatter(); 335 passSeriesFormat.setPointLabelFormatter(new PointLabelFormatter()); 336 passSeriesFormat.configure(getApplicationContext(), 337 R.xml.ultrasound_line_formatter_pass); 338 plot.addSeries(passSeries, passSeriesFormat); 339 } 340 } 341 342 /** 343 * Plays the generated pips. 344 */ play()345 private void play() { 346 play(SoundGenerator.getInstance().getByte(), Common.PLAYING_SAMPLE_RATE_HZ); 347 } 348 349 /** 350 * Plays the sound data. 351 */ play(byte[] data, int sampleRate)352 private void play(byte[] data, int sampleRate) { 353 if (audioTrack != null) { 354 audioTrack.stop(); 355 audioTrack.release(); 356 } 357 audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 358 sampleRate, AudioFormat.CHANNEL_OUT_MONO, 359 AudioFormat.ENCODING_PCM_16BIT, Math.max(data.length, AudioTrack.getMinBufferSize( 360 sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT)), 361 AudioTrack.MODE_STATIC); 362 audioTrack.write(data, 0, data.length); 363 audioTrack.play(); 364 } 365 366 /** 367 * AsyncTask class for the analyzing. 368 */ 369 private class WavAnalyzerTask extends AsyncTask<Void, String, String> 370 implements WavAnalyzer.Listener { 371 372 private static final String TAG = "WavAnalyzerTask"; 373 WavAnalyzer wavAnalyzer; 374 WavAnalyzerTask(byte[] recording)375 public WavAnalyzerTask(byte[] recording) { 376 wavAnalyzer = new WavAnalyzer(recording, Common.RECORDING_SAMPLE_RATE_HZ, 377 WavAnalyzerTask.this); 378 } 379 getDB()380 double[] getDB() { 381 return wavAnalyzer.getDB(); 382 } 383 getPower()384 double[][] getPower() { 385 return wavAnalyzer.getPower(); 386 } 387 getNoiseDB()388 double[] getNoiseDB() { 389 return wavAnalyzer.getNoiseDB(); 390 } 391 getThreshold()392 double getThreshold() { 393 return wavAnalyzer.getThreshold(); 394 } 395 396 @Override doInBackground(Void... params)397 protected String doInBackground(Void... params) { 398 boolean result = wavAnalyzer.doWork(); 399 if (result) { 400 return getString(R.string.hifi_ultrasound_test_pass); 401 } 402 return getString(R.string.hifi_ultrasound_test_fail); 403 } 404 405 @Override onPostExecute(String result)406 protected void onPostExecute(String result) { 407 info.append(result); 408 recorderButton.setEnabled(true); 409 recorderButton.setText(R.string.hifi_ultrasound_test_plot); 410 411 Button okButton = (Button)instruView.findViewById(R.id.ok); 412 okButton.setOnClickListener(new Button.OnClickListener() { 413 @Override 414 public void onClick(View v) { 415 instruWindow.dismiss(); 416 onInstruScreen = false; 417 if (status == HifiUltrasoundSpeakerTestActivity.Status.PLAYER) { 418 playerButton.setEnabled(spkrSupport); 419 } else { 420 recorderButton.setEnabled(micSupport); 421 } 422 } 423 }); 424 TextView instruction = (TextView) instruView.findViewById(R.id.instru); 425 instruction.setText(R.string.hifi_ultrasound_speaker_test_reference_side); 426 instruWindow.showAtLocation(info, Gravity.CENTER, 0, 0); 427 recorderButton.setEnabled(false); 428 playerButton.setEnabled(false); 429 onInstruScreen = true; 430 } 431 432 @Override onProgressUpdate(String... values)433 protected void onProgressUpdate(String... values) { 434 for (String message : values) { 435 info.append(message); 436 Log.d(TAG, message); 437 } 438 } 439 440 @Override sendMessage(String message)441 public void sendMessage(String message) { 442 publishProgress(message); 443 } 444 } 445 } 446