blob: 65452ff0ef4640bac73a7768224d7af318e985fe [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.chromium.latency.walt;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.text.method.ScrollingMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Description;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static org.chromium.latency.walt.Utils.getIntPreference;
/**
* A simple {@link Fragment} subclass.
*/
public class AudioFragment extends Fragment implements View.OnClickListener,
BaseTest.TestStateListener {
enum AudioTestType {
CONTINUOUS_PLAYBACK,
CONTINUOUS_RECORDING,
COLD_PLAYBACK,
COLD_RECORDING,
DISPLAY_WAVEFORM
}
private SimpleLogger logger;
private TextView textView;
private AudioTest audioTest;
private View startButton;
private View stopButton;
private Spinner modeSpinner;
private LineChart chart;
private HistogramChart latencyChart;
private View chartLayout;
private static final int PERMISSION_REQUEST_RECORD_AUDIO = 1;
public AudioFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
logger = SimpleLogger.getInstance(getContext());
audioTest = new AudioTest(getActivity());
audioTest.setTestStateListener(this);
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_audio, container, false);
textView = (TextView) view.findViewById(R.id.txt_box_audio);
textView.setMovementMethod(new ScrollingMovementMethod());
startButton = view.findViewById(R.id.button_start_audio);
stopButton = view.findViewById(R.id.button_stop_audio);
chartLayout = view.findViewById(R.id.chart_layout);
chart = (LineChart) view.findViewById(R.id.chart);
latencyChart = (HistogramChart) view.findViewById(R.id.latency_chart);
view.findViewById(R.id.button_close_chart).setOnClickListener(this);
enableButtons();
// Configure the audio mode spinner
modeSpinner = (Spinner) view.findViewById(R.id.spinner_audio_mode);
ArrayAdapter<CharSequence> modeAdapter = ArrayAdapter.createFromResource(getContext(),
R.array.audio_mode_array, android.R.layout.simple_spinner_item);
modeAdapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item);
modeSpinner.setAdapter(modeAdapter);
return view;
}
@Override
public void onResume() {
super.onResume();
// Register this fragment class as the listener for some button clicks
startButton.setOnClickListener(this);
stopButton.setOnClickListener(this);
textView.setText(logger.getLogText());
logger.registerReceiver(logReceiver);
}
@Override
public void onPause() {
logger.unregisterReceiver(logReceiver);
super.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
audioTest.teardown();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_start_audio:
chartLayout.setVisibility(View.GONE);
disableButtons();
AudioTestType testType = getSelectedTestType();
switch (testType) {
case CONTINUOUS_PLAYBACK:
case CONTINUOUS_RECORDING:
case DISPLAY_WAVEFORM:
audioTest.setAudioMode(AudioTest.AudioMode.CONTINUOUS);
audioTest.setPeriod(AudioTest.CONTINUOUS_TEST_PERIOD);
break;
case COLD_PLAYBACK:
case COLD_RECORDING:
audioTest.setAudioMode(AudioTest.AudioMode.CONTINUOUS);
audioTest.setPeriod(AudioTest.COLD_TEST_PERIOD);
break;
}
if (testType == AudioTestType.DISPLAY_WAVEFORM) {
// Only need to record 1 beep to display wave
audioTest.setRecordingRepetitions(1);
} else {
audioTest.setRecordingRepetitions(
getIntPreference(getContext(), R.string.preference_audio_in_reps, 5));
}
if (testType == AudioTestType.CONTINUOUS_PLAYBACK ||
testType == AudioTestType.COLD_PLAYBACK ||
testType == AudioTestType.CONTINUOUS_RECORDING ||
testType == AudioTestType.COLD_RECORDING) {
latencyChart.setVisibility(View.VISIBLE);
latencyChart.clearData();
latencyChart.setLegendEnabled(false);
final String description =
getResources().getStringArray(R.array.audio_mode_array)[
modeSpinner.getSelectedItemPosition()] + " [ms]";
latencyChart.setDescription(description);
}
switch (testType) {
case CONTINUOUS_RECORDING:
case COLD_RECORDING:
case DISPLAY_WAVEFORM:
attemptRecordingTest();
break;
case CONTINUOUS_PLAYBACK:
case COLD_PLAYBACK:
// Set media volume to max
AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_MUSIC, am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
audioTest.beginPlaybackMeasurement();
break;
}
break;
case R.id.button_stop_audio:
audioTest.stopTest();
break;
case R.id.button_close_chart:
chartLayout.setVisibility(View.GONE);
break;
}
}
private AudioTestType getSelectedTestType() {
return AudioTestType.values()[modeSpinner.getSelectedItemPosition()];
}
private BroadcastReceiver logReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("message");
textView.append(msg + "\n");
}
};
private void attemptRecordingTest() {
// first see if we already have permission to record audio
int currentPermission = ContextCompat.checkSelfPermission(this.getContext(),
Manifest.permission.RECORD_AUDIO);
if (currentPermission == PackageManager.PERMISSION_GRANTED) {
disableButtons();
audioTest.beginRecordingMeasurement();
} else {
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
PERMISSION_REQUEST_RECORD_AUDIO);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_RECORD_AUDIO:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
disableButtons();
audioTest.beginRecordingMeasurement();
} else {
logger.log("Could not get permission to record audio");
}
return;
}
}
@Override
public void onTestStopped() {
if (getSelectedTestType() == AudioTestType.DISPLAY_WAVEFORM) {
drawWaveformChart();
} else {
if (!audioTest.deltas_mic.isEmpty()) {
latencyChart.setLegendEnabled(true);
latencyChart.setLabel(String.format(Locale.US, "Median=%.1f ms", Utils.median(audioTest.deltas_mic)));
} else if (!audioTest.deltas_queue2wire.isEmpty()) {
latencyChart.setLegendEnabled(true);
latencyChart.setLabel(String.format(Locale.US, "Median=%.1f ms", Utils.median(audioTest.deltas_queue2wire)));
}
}
LogUploader.uploadIfAutoEnabled(getContext());
enableButtons();
}
@Override
public void onTestStoppedWithError() {
enableButtons();
latencyChart.setVisibility(View.GONE);
}
@Override
public void onTestPartialResult(double value) {
latencyChart.addEntry(value);
}
private void drawWaveformChart() {
final short[] wave = AudioTest.getRecordedWave();
List<Entry> entries = new ArrayList<>();
int frameRate = audioTest.getOptimalFrameRate();
for (int i = 0; i < wave.length; i++) {
float timeStamp = (float) i / frameRate * 1000f;
entries.add(new Entry(timeStamp, (float) wave[i]));
}
LineDataSet dataSet = new LineDataSet(entries, "Waveform");
dataSet.setColor(Color.BLACK);
dataSet.setValueTextColor(Color.BLACK);
dataSet.setCircleColor(ContextCompat.getColor(getContext(), R.color.DarkGreen));
dataSet.setCircleRadius(1.5f);
dataSet.setCircleColorHole(Color.DKGRAY);
LineData lineData = new LineData(dataSet);
chart.setData(lineData);
LimitLine line = new LimitLine(audioTest.getThreshold(), "Threshold");
line.setLineColor(Color.RED);
line.setLabelPosition(LimitLine.LimitLabelPosition.LEFT_TOP);
line.setLineWidth(2f);
line.setTextColor(Color.DKGRAY);
line.setTextSize(10f);
chart.getAxisLeft().addLimitLine(line);
final Description desc = new Description();
desc.setText("Wave [digital level -32768 to +32767] vs. Time [ms]");
desc.setTextSize(12f);
chart.setDescription(desc);
chart.getLegend().setEnabled(false);
chart.invalidate();
chartLayout.setVisibility(View.VISIBLE);
}
private void disableButtons() {
startButton.setEnabled(false);
stopButton.setEnabled(true);
}
private void enableButtons() {
startButton.setEnabled(true);
stopButton.setEnabled(false);
}
}