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