blob: 7f96464b2021c7c027c1d60f0f53fc169ee584ac [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 com.android.cts.verifier.audio;
import android.app.AlertDialog;
import android.content.Context;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import com.android.compatibility.common.util.ReportLog;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
/**
* Base class for testing activitiees that require audio loopback hardware..
*/
public class AudioLoopbackBaseActivity extends PassFailButtons.Activity {
private static final String TAG = "AudioLoopbackActivity";
protected AudioManager mAudioManager;
// UI
OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
Button mLoopbackPortYesBtn;
Button mLoopbackPortNoBtn;
TextView mInputDeviceTxt;
TextView mOutputDeviceTxt;
TextView mAudioLevelText;
SeekBar mAudioLevelSeekbar;
TextView mResultText;
ProgressBar mProgressBar;
int mMaxLevel;
// Peripheral(s)
boolean mIsPeripheralAttached; // CDD ProAudio section C-1-3
AudioDeviceInfo mOutputDevInfo;
AudioDeviceInfo mInputDevInfo;
// Loopback Logic
NativeAnalyzerThread mNativeAnalyzerThread = null;
protected double mLatencyMillis = 0.0;
protected double mConfidence = 0.0;
protected static final double CONFIDENCE_THRESHOLD = 0.6;
protected static final double PROAUDIO_LATENCY_MS_LIMIT = 20.0;
// The audio stream callback threads should stop and close
// in less than a few hundred msec. This is a generous timeout value.
private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
//
// Common UI Handling
//
void enableLayout(int layoutId, boolean enable) {
ViewGroup group = (ViewGroup)findViewById(layoutId);
for (int i = 0; i < group.getChildCount(); i++) {
group.getChildAt(i).setEnabled(enable);
}
}
private void connectLoopbackUI() {
// Has Loopback - Yes
mLoopbackPortYesBtn = (Button)findViewById(R.id.loopback_tests_yes_btn);
mLoopbackPortYesBtn.setOnClickListener(mBtnClickListener);
// Has Looback - No
mLoopbackPortNoBtn = (Button)findViewById(R.id.loopback_tests_no_btn);
mLoopbackPortNoBtn.setOnClickListener(mBtnClickListener);
// Connected Device
mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
// Loopback Info
findViewById(R.id.loopback_tests_info_btn).setOnClickListener(mBtnClickListener);
mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mAudioLevelSeekbar.setMax(mMaxLevel);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
refreshLevel();
mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
progress, 0);
refreshLevel();
Log.i(TAG,"Changed stream volume to: " + progress);
}
});
mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
showWait(false);
enableTestUI(false);
}
//
// Peripheral Connection Logic
//
protected void scanPeripheralList(AudioDeviceInfo[] devices) {
// CDD Section C-1-3: USB port, host-mode support
// Can't just use the first record because then we will only get
// Source OR sink, not both even on devices that are both.
mOutputDevInfo = null;
mInputDevInfo = null;
// Any valid peripherals
// Do we leave in the Headset test to support a USB-Dongle?
for (AudioDeviceInfo devInfo : devices) {
if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
if (devInfo.isSink()) {
mOutputDevInfo = devInfo;
}
if (devInfo.isSource()) {
mInputDevInfo = devInfo;
}
} else {
handleDeviceConnection(devInfo);
}
}
mIsPeripheralAttached = mOutputDevInfo != null || mInputDevInfo != null;
showConnectedAudioPeripheral();
}
protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
// NOP
}
private class ConnectListener extends AudioDeviceCallback {
/*package*/ ConnectListener() {}
//
// AudioDevicesManager.OnDeviceConnectionListener
//
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
}
@Override
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
}
}
protected void showConnectedAudioPeripheral() {
mInputDeviceTxt.setText(
mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
: "");
mOutputDeviceTxt.setText(
mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
: "");
}
/**
* refresh Audio Level seekbar and text
*/
private void refreshLevel() {
int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
mAudioLevelSeekbar.setProgress(currentLevel);
String levelText = String.format("%s: %d/%d",
getResources().getString(R.string.audio_loopback_level_text),
currentLevel, mMaxLevel);
mAudioLevelText.setText(levelText);
}
private void showLoopbackInfoDialog() {
new AlertDialog.Builder(this)
.setTitle(R.string.loopback_dlg_caption)
.setMessage(R.string.loopback_dlg_text)
.setPositiveButton(R.string.audio_general_ok, null)
.show();
}
//
// show active progress bar
//
protected void showWait(boolean show) {
if (show) {
mProgressBar.setVisibility(View.VISIBLE) ;
} else {
mProgressBar.setVisibility(View.INVISIBLE) ;
}
}
private class OnBtnClickListener implements OnClickListener {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.loopback_tests_yes_btn:
Log.i(TAG, "User confirms Headset Port existence");
recordLoopbackStatus(true);
mLoopbackPortYesBtn.setEnabled(false);
mLoopbackPortNoBtn.setEnabled(false);
break;
case R.id.loopback_tests_no_btn:
Log.i(TAG, "User denies Headset Port existence");
recordLoopbackStatus(false);
getPassButton().setEnabled(true);
mLoopbackPortYesBtn.setEnabled(false);
mLoopbackPortNoBtn.setEnabled(false);
break;
case R.id.loopback_tests_info_btn:
showLoopbackInfoDialog();
break;
}
}
}
//
// Common loging
//
protected void recordTestResults() {
ReportLog reportLog = getReportLog();
reportLog.addValue(
"Estimated Latency",
mLatencyMillis,
ResultType.LOWER_BETTER,
ResultUnit.MS);
reportLog.addValue(
"Confidence",
mConfidence,
ResultType.HIGHER_BETTER,
ResultUnit.NONE);
reportLog.addValue(
"Sample Rate",
mNativeAnalyzerThread.getSampleRate(),
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
"Is Peripheral Attached",
mIsPeripheralAttached,
ResultType.NEUTRAL,
ResultUnit.NONE);
if (mIsPeripheralAttached) {
reportLog.addValue(
"Input Device",
mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.addValue(
"Ouput Device",
mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
ResultType.NEUTRAL,
ResultUnit.NONE);
}
}
//
// test logic
//
protected void startAudioTest(Handler messageHandler) {
getPassButton().setEnabled(false);
mLatencyMillis = 0.0;
mConfidence = 0.0;
mNativeAnalyzerThread = new NativeAnalyzerThread();
if (mNativeAnalyzerThread != null) {
mNativeAnalyzerThread.setMessageHandler(messageHandler);
// This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
mNativeAnalyzerThread.startTest();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
protected void handleTestCompletion() {
// Make sure the test thread is finished. It should already be done.
if (mNativeAnalyzerThread != null) {
try {
mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* handler for messages from audio thread
*/
protected Handler mMessageHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch(msg.what) {
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
Log.v(TAG,"got message native rec started!!");
showWait(true);
mResultText.setText("Test Running...");
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
Log.v(TAG,"got message native rec can't start!!");
mResultText.setText("Test Error opening streams.");
handleTestCompletion();
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
Log.v(TAG,"got message native rec can't start!!");
mResultText.setText("Test Error while recording.");
handleTestCompletion();
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
mResultText.setText("Test FAILED due to errors.");
handleTestCompletion();
break;
case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
if (mNativeAnalyzerThread != null) {
mLatencyMillis = mNativeAnalyzerThread.getLatencyMillis();
mConfidence = mNativeAnalyzerThread.getConfidence();
}
mResultText.setText(String.format(
"Test Finished\nLatency:%.2f ms\nConfidence: %.2f",
mLatencyMillis,
mConfidence));
handleTestCompletion();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
connectLoopbackUI();
}
private void recordLoopbackStatus(boolean has) {
getReportLog().addValue(
"User reported loopback availability: ",
has ? 1.0 : 0,
ResultType.NEUTRAL,
ResultUnit.NONE);
}
//
// Overrides
//
void enableTestUI(boolean enable) {
}
}