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