/* * Copyright (C) 2009 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.scoaudiotest; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.AssetFileDescriptor; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaRecorder; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import android.widget.ToggleButton; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Locale; public class ScoAudioTest extends Activity { final static String TAG = "ScoAudioTest"; AudioManager mAudioManager; AudioManager mAudioManager2; boolean mForceScoOn; ToggleButton mScoButton; ToggleButton mVoiceDialerButton; boolean mVoiceDialerOn; String mLastRecordedFile; SimpleMediaController mMediaControllers[] = new SimpleMediaController[2]; private TextToSpeech mTts; private HashMap mTtsParams; private int mOriginalVoiceVolume; EditText mSpeakText; boolean mTtsInited; private Handler mHandler; private static final String UTTERANCE = "utterance"; private static Intent sVoiceCommandIntent; private File mSampleFile; ToggleButton mTtsToFileButton; private boolean mTtsToFile; private int mCurrentMode; Spinner mModeSpinner; private BluetoothHeadset mBluetoothHeadset; private BluetoothDevice mBluetoothHeadsetDevice; TextView mScoStateTxt; TextView mVdStateTxt; private final BroadcastReceiver mReceiver = new ScoBroadcastReceiver(); public ScoAudioTest() { Log.e(TAG, "contructor"); } /** Called when the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.scoaudiotest); mScoStateTxt = (TextView) findViewById(R.id.scoStateTxt); mVdStateTxt = (TextView) findViewById(R.id.vdStateTxt); IntentFilter intentFilter = new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED); intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); registerReceiver(mReceiver, intentFilter); mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mAudioManager2 = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE); mHandler = new Handler(); mMediaControllers[0] = new SimplePlayerController(this, R.id.playPause1, R.id.stop1, R.raw.sine440_mo_16b_16k, AudioManager.STREAM_BLUETOOTH_SCO); TextView name = (TextView) findViewById(R.id.playPause1Text); name.setText("VOICE_CALL stream"); mScoButton = (ToggleButton)findViewById(R.id.ForceScoButton); mScoButton.setOnCheckedChangeListener(mForceScoChanged); mForceScoOn = false; mScoButton.setChecked(mForceScoOn); mVoiceDialerButton = (ToggleButton)findViewById(R.id.VoiceDialerButton); mVoiceDialerButton.setOnCheckedChangeListener(mVoiceDialerChanged); mVoiceDialerOn = false; mVoiceDialerButton.setChecked(mVoiceDialerOn); mMediaControllers[1] = new SimpleRecordController(this, R.id.recStop1, 0, "Sco_record_"); mTtsInited = false; mTts = new TextToSpeech(this, new TtsInitListener()); mTtsParams = new HashMap(); mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, String.valueOf(AudioManager.STREAM_BLUETOOTH_SCO)); mTtsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, UTTERANCE); mSpeakText = (EditText) findViewById(R.id.speakTextEdit); mSpeakText.setOnKeyListener(mSpeakKeyListener); mSpeakText.setText("sco audio test sentence"); mTtsToFileButton = (ToggleButton)findViewById(R.id.TtsToFileButton); mTtsToFileButton.setOnCheckedChangeListener(mTtsToFileChanged); mTtsToFile = true; mTtsToFileButton.setChecked(mTtsToFile); mModeSpinner = (Spinner) findViewById(R.id.modeSpinner); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, mModeStrings); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mModeSpinner.setAdapter(adapter); mModeSpinner.setOnItemSelectedListener(mModeChanged); mCurrentMode = mAudioManager.getMode(); mModeSpinner.setSelection(mCurrentMode); mBluetoothHeadsetDevice = null; BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); if (btAdapter != null) { btAdapter.getProfileProxy(this, mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); } sVoiceCommandIntent = new Intent(Intent.ACTION_VOICE_COMMAND); sVoiceCommandIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } @Override public void onDestroy() { super.onDestroy(); mTts.shutdown(); unregisterReceiver(mReceiver); if (mBluetoothHeadset != null) { BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); if (btAdapter != null) { btAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset); } } } @Override protected void onPause() { super.onPause(); // mForceScoOn = false; // mScoButton.setChecked(mForceScoOn); mMediaControllers[0].stop(); mMediaControllers[1].stop(); mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mOriginalVoiceVolume, 0); } @Override protected void onResume() { super.onResume(); mLastRecordedFile = ""; mMediaControllers[0].mFileName = ""; mOriginalVoiceVolume = mAudioManager.getStreamVolume( AudioManager.STREAM_BLUETOOTH_SCO); setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO); mCurrentMode = mAudioManager.getMode(); mModeSpinner.setSelection(mCurrentMode); } private OnCheckedChangeListener mForceScoChanged = new OnCheckedChangeListener(){ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mForceScoOn != isChecked) { mForceScoOn = isChecked; AudioManager mngr = mAudioManager; boolean useVirtualCall = false; CheckBox box = (CheckBox) findViewById(R.id.useSecondAudioManager); if (box.isChecked()) { Log.i(TAG, "Using 2nd audio manager"); mngr = mAudioManager2; } box = (CheckBox) findViewById(R.id.useVirtualCallCheckBox); useVirtualCall = box.isChecked(); if (mForceScoOn) { if (useVirtualCall) { Log.e(TAG, "startBluetoothScoVirtualCall() IN"); mngr.startBluetoothScoVirtualCall(); Log.e(TAG, "startBluetoothScoVirtualCall() OUT"); } else { Log.e(TAG, "startBluetoothSco() IN"); mngr.startBluetoothSco(); Log.e(TAG, "startBluetoothSco() OUT"); } } else { Log.e(TAG, "stopBluetoothSco() IN"); mngr.stopBluetoothSco(); Log.e(TAG, "stopBluetoothSco() OUT"); } } } }; private OnCheckedChangeListener mVoiceDialerChanged = new OnCheckedChangeListener(){ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (mVoiceDialerOn != isChecked) { mVoiceDialerOn = isChecked; if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) { if (mVoiceDialerOn) { mBluetoothHeadset.startVoiceRecognition(mBluetoothHeadsetDevice); } else { mBluetoothHeadset.stopVoiceRecognition(mBluetoothHeadsetDevice); } } } } }; private OnCheckedChangeListener mTtsToFileChanged = new OnCheckedChangeListener(){ @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mTtsToFile = isChecked; } }; private class SimpleMediaController implements OnClickListener { int mPlayPauseButtonId; int mStopButtonId; Context mContext; ImageView mPlayPauseButton; int mPlayImageResource; int mPauseImageResource; String mFileNameBase; String mFileName; int mFileResId; SimpleMediaController(Context context, int playPausebuttonId, int stopButtonId, String fileName) { mContext = context; mPlayPauseButtonId = playPausebuttonId; mStopButtonId = stopButtonId; mFileNameBase = fileName; mPlayPauseButton = (ImageButton) findViewById(playPausebuttonId); ImageButton stop = (ImageButton) findViewById(stopButtonId); mPlayPauseButton.setOnClickListener(this); mPlayPauseButton.requestFocus(); if (stop != null) { stop.setOnClickListener(this); } } SimpleMediaController(Context context, int playPausebuttonId, int stopButtonId, int fileResId) { mContext = context; mPlayPauseButtonId = playPausebuttonId; mStopButtonId = stopButtonId; mFileNameBase = ""; mFileResId = fileResId; mPlayPauseButton = (ImageButton) findViewById(playPausebuttonId); ImageButton stop = (ImageButton) findViewById(stopButtonId); mPlayPauseButton.setOnClickListener(this); mPlayPauseButton.requestFocus(); if (stop != null) { stop.setOnClickListener(this); } } @Override public void onClick(View v) { if (v.getId() == mPlayPauseButtonId) { playOrPause(); } else if (v.getId() == mStopButtonId) { stop(); } } public void playOrPause() { } public void stop() { } public boolean isPlaying() { return false; } public void updatePlayPauseButton() { mPlayPauseButton.setImageResource(isPlaying() ? mPauseImageResource : mPlayImageResource); } } private class SimplePlayerController extends SimpleMediaController { private MediaPlayer mMediaPlayer; private int mStreamType; SimplePlayerController(Context context, int playPausebuttonId, int stopButtonId, String fileName, int stream) { super(context, playPausebuttonId, stopButtonId, fileName); mPlayImageResource = android.R.drawable.ic_media_play; mPauseImageResource = android.R.drawable.ic_media_pause; mStreamType = stream; mFileName = Environment.getExternalStorageDirectory().toString() + "/music/" + mFileNameBase + "_" + ".wav"; } SimplePlayerController(Context context, int playPausebuttonId, int stopButtonId, int fileResId, int stream) { super(context, playPausebuttonId, stopButtonId, fileResId); mPlayImageResource = android.R.drawable.ic_media_play; mPauseImageResource = android.R.drawable.ic_media_pause; mStreamType = stream; mFileName = ""; } @Override public void playOrPause() { Log.e(TAG, "playOrPause playing: "+((mMediaPlayer == null)?false:!mMediaPlayer.isPlaying())+ " mMediaPlayer: "+mMediaPlayer+ " mFileName: "+mFileName+ " mLastRecordedFile: "+mLastRecordedFile); if (mMediaPlayer == null || !mMediaPlayer.isPlaying()){ if (mMediaPlayer == null) { if (mFileName != mLastRecordedFile) { mFileName = mLastRecordedFile; Log.e(TAG, "new recorded file: "+mFileName); } try { mMediaPlayer = new MediaPlayer(); if (mFileName.equals("")) { Log.e(TAG, "Playing from resource"); AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(mFileResId); mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); afd.close(); } else { Log.e(TAG, "Playing file: "+mFileName); mMediaPlayer.setDataSource(mFileName); } mMediaPlayer.setAudioStreamType(mStreamType); mMediaPlayer.prepare(); mMediaPlayer.setLooping(true); } catch (Exception ex) { Log.e(TAG, "mMediaPlayercreate failed:", ex); mMediaPlayer.release(); mMediaPlayer = null; } if (mMediaPlayer != null) { mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { updatePlayPauseButton(); } }); } } if (mMediaPlayer != null) { mMediaPlayer.start(); } } else { mMediaPlayer.pause(); } updatePlayPauseButton(); } @Override public void stop() { if (mMediaPlayer != null) { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; } updatePlayPauseButton(); } @Override public boolean isPlaying() { if (mMediaPlayer != null) { return mMediaPlayer.isPlaying(); } else { return false; } } } private class SimpleRecordController extends SimpleMediaController { private MediaRecorder mMediaRecorder; private int mFileCount = 0; private int mState = 0; SimpleRecordController(Context context, int playPausebuttonId, int stopButtonId, String fileName) { super(context, playPausebuttonId, stopButtonId, fileName); Log.e(TAG, "SimpleRecordController cstor"); mPlayImageResource = R.drawable.record; mPauseImageResource = R.drawable.stop; } @Override public void playOrPause() { if (mState == 0) { setup(); try { mMediaRecorder.start(); mState = 1; } catch (Exception e) { Log.e(TAG, "Could start MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; mState = 0; } } else { try { mMediaRecorder.stop(); mMediaRecorder.reset(); } catch (Exception e) { Log.e(TAG, "Could not stop MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; } finally { mState = 0; } } updatePlayPauseButton(); } public void setup() { Log.e(TAG, "SimpleRecordController setup()"); if (mMediaRecorder == null) { mMediaRecorder = new MediaRecorder(); } mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mFileName = Environment.getExternalStorageDirectory().toString() + "/music/" + mFileNameBase + "_" + ++mFileCount + ".amr"; mLastRecordedFile = mFileName; Log.e(TAG, "recording to file: "+mLastRecordedFile); mMediaRecorder.setOutputFile(mFileName); try { mMediaRecorder.prepare(); } catch (Exception e) { Log.e(TAG, "Could not prepare MediaRecorder: ", e); mMediaRecorder.release(); mMediaRecorder = null; } } @Override public void stop() { if (mMediaRecorder != null) { try { mMediaRecorder.stop(); } catch (Exception e) { Log.e(TAG, "Could not stop MediaRecorder: ", e); } finally { mMediaRecorder.release(); mMediaRecorder = null; } } updatePlayPauseButton(); } @Override public boolean isPlaying() { if (mState == 1) { return true; } else { return false; } } } class TtsInitListener implements TextToSpeech.OnInitListener { @Override public void onInit(int status) { // status can be either TextToSpeech.SUCCESS or TextToSpeech.ERROR. Log.e(TAG, "onInit for tts"); if (status != TextToSpeech.SUCCESS) { // Initialization failed. Log.e(TAG, "Could not initialize TextToSpeech."); return; } if (mTts == null) { Log.e(TAG, "null tts"); return; } int result = mTts.setLanguage(Locale.US); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { // Lanuage data is missing or the language is not supported. Log.e(TAG, "Language is not available."); return; } mTts.setOnUtteranceCompletedListener(new MyUtteranceCompletedListener(UTTERANCE)); mTtsInited = true; } } class MyUtteranceCompletedListener implements OnUtteranceCompletedListener { private final String mExpectedUtterance; public MyUtteranceCompletedListener(String expectedUtteranceId) { mExpectedUtterance = expectedUtteranceId; } @Override public void onUtteranceCompleted(String utteranceId) { Log.e(TAG, "onUtteranceCompleted " + utteranceId); if (mTtsToFile) { if (mSampleFile != null && mSampleFile.exists()) { MediaPlayer mediaPlayer = new MediaPlayer(); try { mediaPlayer.setDataSource(mSampleFile.getPath()); mediaPlayer.setAudioStreamType(AudioManager.STREAM_BLUETOOTH_SCO); mediaPlayer.prepare(); } catch (Exception ex) { Log.e(TAG, "mMediaPlayercreate failed:", ex); mediaPlayer.release(); mediaPlayer = null; } if (mediaPlayer != null) { mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { mp.release(); if (mSampleFile != null && mSampleFile.exists()) { mSampleFile.delete(); mSampleFile = null; } mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mOriginalVoiceVolume, 0); // Debug.stopMethodTracing(); } }); mediaPlayer.start(); } } else { Log.e(TAG, "synthesizeToFile did not create file"); } } else { mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mOriginalVoiceVolume, 0); // Debug.stopMethodTracing(); } Log.e(TAG, "end speak, volume: "+mOriginalVoiceVolume); } } private View.OnKeyListener mSpeakKeyListener = new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: if (!mTtsInited) { Log.e(TAG, "Tts not inited "); return false; } mOriginalVoiceVolume = mAudioManager.getStreamVolume( AudioManager.STREAM_BLUETOOTH_SCO); Log.e(TAG, "start speak, volume: "+mOriginalVoiceVolume); mAudioManager.setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, mOriginalVoiceVolume/2, 0); // we now have SCO connection and TTS, so we can start. mHandler.post(new Runnable() { @Override public void run() { // Debug.startMethodTracing("tts"); if (mTtsToFile) { if (mSampleFile != null && mSampleFile.exists()) { mSampleFile.delete(); mSampleFile = null; } mSampleFile = new File(Environment.getExternalStorageDirectory(), "mytts.wav"); mTts.synthesizeToFile(mSpeakText.getText().toString(), mTtsParams, mSampleFile.getPath()); } else { mTts.speak(mSpeakText.getText().toString(), TextToSpeech.QUEUE_FLUSH, mTtsParams); } } }); return true; } } return false; } }; private static final String[] mModeStrings = { "NORMAL", "RINGTONE", "IN_CALL", "IN_COMMUNICATION" }; private Spinner.OnItemSelectedListener mModeChanged = new Spinner.OnItemSelectedListener() { @Override public void onItemSelected(android.widget.AdapterView av, View v, int position, long id) { if (mCurrentMode != position) { mCurrentMode = position; mAudioManager.setMode(mCurrentMode); } } @Override public void onNothingSelected(android.widget.AdapterView av) { } }; private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = new BluetoothProfile.ServiceListener() { @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { mBluetoothHeadset = (BluetoothHeadset) proxy; List deviceList = mBluetoothHeadset.getConnectedDevices(); if (deviceList.size() > 0) { mBluetoothHeadsetDevice = deviceList.get(0); } else { mBluetoothHeadsetDevice = null; } } @Override public void onServiceDisconnected(int profile) { if (mBluetoothHeadset != null) { List devices = mBluetoothHeadset.getConnectedDevices(); if (devices.size() == 0) { mBluetoothHeadsetDevice = null; } mBluetoothHeadset = null; } } }; private int mChangedState = -1; private int mUpdatedState = -1; private int mUpdatedPrevState = -1; private class ScoBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); mVdStateTxt.setText(Integer.toString(state)); Log.e(TAG, "BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED: "+state); } else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED)) { mChangedState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1); Log.e(TAG, "ACTION_SCO_AUDIO_STATE_CHANGED: "+mChangedState); mScoStateTxt.setText("changed: "+Integer.toString(mChangedState)+ " updated: "+Integer.toString(mUpdatedState)+ " prev updated: "+Integer.toString(mUpdatedPrevState)); } else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) { mUpdatedState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1); mUpdatedPrevState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, -1); Log.e(TAG, "ACTION_SCO_AUDIO_STATE_UPDATED, state: "+mUpdatedState+" prev state: "+mUpdatedPrevState); mScoStateTxt.setText("changed: "+Integer.toString(mChangedState)+ " updated: "+Integer.toString(mUpdatedState)+ " prev updated: "+Integer.toString(mUpdatedPrevState)); if (mForceScoOn && mUpdatedState == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) { mForceScoOn = false; mScoButton.setChecked(mForceScoOn); mAudioManager.stopBluetoothSco(); } } } } }