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