diff options
author | Khalid Zubair <kzubair@cyngn.com> | 2016-06-30 12:25:15 -0700 |
---|---|---|
committer | Roman Birg <roman@cyngn.com> | 2016-06-30 15:48:39 -0700 |
commit | c8b4cf4c6901ca7a675d9a43f9b2eb4b4d95065d (patch) | |
tree | 18e564d6c63eff04cb3c4e108b9f6e4d221b8206 | |
parent | 02cf1e2913b73fd3de3976aaaf08f84601c69bec (diff) | |
download | android_packages_apps_SoundRecorder-c8b4cf4c6901ca7a675d9a43f9b2eb4b4d95065d.tar.gz android_packages_apps_SoundRecorder-c8b4cf4c6901ca7a675d9a43f9b2eb4b4d95065d.tar.bz2 android_packages_apps_SoundRecorder-c8b4cf4c6901ca7a675d9a43f9b2eb4b4d95065d.zip |
Add Bluetooth sound recording
Connect to available Bluetooth headsets when starting a sound
recording. The sound recording is stopped if the headset disconnects.
CYNGNOS-883
Change-Id: I0b0790a07421eec9d0c9af2615f363c040e1e74d
-rw-r--r-- | AndroidManifest.xml | 2 | ||||
-rw-r--r-- | res/values/cm_strings.xml | 9 | ||||
-rw-r--r-- | src/com/android/soundrecorder/SoundRecorder.java | 138 |
3 files changed, 149 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8f55baf..e469032 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -30,6 +30,8 @@ <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" /> + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <application android:label="@string/app_name" android:icon="@mipmap/ic_launcher" diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml index fd04503..714b9e0 100644 --- a/res/values/cm_strings.xml +++ b/res/values/cm_strings.xml @@ -71,4 +71,13 @@ <string name="notification_stopped_title">Recording stopped</string> <string name="notification_stopped_content">Interrupted by call or music</string> + + <!-- shown as a toast when a recording is started with a bluetooth headset as the input source --> + <string name="bt_using_headset">Using bluetooth headset</string> + <!-- shown as a toast when initiating a connection to a bluetooth headset --> + <string name="bt_headset_connecting">Connecting to headset</string> + <!-- shown as a toast when recording is stopped because a bluetooth connection is dropped --> + <string name="bt_headset_disconnected">Bluetooth connection lost</string> + <!-- shown as a toast when recording fails to start because a bluetooth connection was not started --> + <string name="bt_headset_timeout">Bluetooth connection timeout</string> </resources> diff --git a/src/com/android/soundrecorder/SoundRecorder.java b/src/com/android/soundrecorder/SoundRecorder.java index bb171f2..4bd7237 100644 --- a/src/com/android/soundrecorder/SoundRecorder.java +++ b/src/com/android/soundrecorder/SoundRecorder.java @@ -31,6 +31,10 @@ import android.app.Dialog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.ContentValues; @@ -88,6 +92,11 @@ import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.telephony.SubscriptionManager; +import static android.media.AudioManager.SCO_AUDIO_STATE_DISCONNECTED; +import static android.media.AudioManager.SCO_AUDIO_STATE_CONNECTED; +import static android.media.AudioManager.SCO_AUDIO_STATE_CONNECTING; + + /** * Calculates remaining recording time based on available disk space and * optionally a maximum recording file size. @@ -278,6 +287,7 @@ public class SoundRecorder extends Activity static final int STOP_RECORDING = 1; static final int STOP_PLAYBACK = 2; static final int START_RECORDING = 3; + static final int START_RECORDING_WITH_BT = 31; static final int START_PLAYBACK = 4; static final int PAUSE_RECORDING = 5; static final int RESUME_RECORDING = 6; @@ -285,6 +295,7 @@ public class SoundRecorder extends Activity static final int ERROR = 8; static final int STOP = 9; static final int STOP_AND_SAVE = 10; + static final int BLUETOOTH_SCO_MODE_TIMEDOUT = 11; static final String STORAGE_PATH_LOCAL_PHONE = Environment.getExternalStorageDirectory() .toString(); @@ -360,6 +371,17 @@ public class SoundRecorder extends Activity private PhoneStateListener[] mPhoneStateListener; + private static final boolean BLUETOOTH_RECORDING_ENABLED = true; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothHeadset mBluetoothHeadset; + + private int mBluetoothScoState = -1; + private int mBluetoothScoPendingRecordArg1; + private int mBluetoothScoPendingRecordArg2; + private Toast mBluetoothConnectingToast; + private boolean mBluetoothScoStarted; + private static final int BLUETOOTH_SCO_MODE_TIMEOUT_DUR = 5000; + private PhoneStateListener getPhoneStateListener(int subId) { PhoneStateListener phoneStateListener = new PhoneStateListener(subId) { @Override @@ -477,6 +499,18 @@ public class SoundRecorder extends Activity bSSRSupported = false; } + if (BLUETOOTH_RECORDING_ENABLED) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBluetoothAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET); + registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onBluetoothScoStateChanged( + intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)); + } + }, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)); + } + View menuButton = findViewById(R.id.menu_button); setupFakeOverflowMenuButton(menuButton); @@ -525,6 +559,21 @@ public class SoundRecorder extends Activity mRecorder.stopPlayback(); break; case START_RECORDING: + if (BLUETOOTH_RECORDING_ENABLED) { + if (mAudioSourceType == MediaRecorder.AudioSource.MIC + && startBluetoothSco()) { + // We just started an async bluetooth connection request + // save the arguments and invoke START_RECORDING_WITH_BT + // when the connection is up. We persist these args so we can + // finish the request after BT is setup (an indeterminate + // length of time). + mBluetoothScoPendingRecordArg1 = msg.arg1; + mBluetoothScoPendingRecordArg2 = msg.arg2; + break; + } + } + // fall-thru + case START_RECORDING_WITH_BT: switch (msg.arg1) { case MediaRecorder.OutputFormat.RAW_AMR: startRecordingAMR(); @@ -554,11 +603,25 @@ public class SoundRecorder extends Activity if (mRecorder.sampleLength() > 0) { mRecorderStop = true; } + if (mBluetoothScoStarted) { + Log.i(TAG, "Stopping Bluetooth SCO Mode"); + mAudioManager.stopBluetoothSco(); + mBluetoothScoStarted = false; + } break; case STOP_AND_SAVE: mRecorder.stop(); saveSample(); break; + + case BLUETOOTH_SCO_MODE_TIMEDOUT: + Log.w(TAG, "Bluetooth SCO mode timed out"); + mBluetoothScoStarted = false; + mAudioManager.stopBluetoothSco(); + Toast.makeText(SoundRecorder.this, + R.string.bt_headset_timeout, Toast.LENGTH_SHORT).show(); + break; + default: break; } @@ -568,6 +631,78 @@ public class SoundRecorder extends Activity mUiHandler.post(mUpdateUiRunnable); } + private BluetoothProfile.ServiceListener mProfileListener = + new BluetoothProfile.ServiceListener() { + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.HEADSET) { + mBluetoothHeadset = (BluetoothHeadset) proxy; + } + } + public void onServiceDisconnected(int profile) { + if (profile == BluetoothProfile.HEADSET) { + mBluetoothHeadset = null; + } + } + }; + + // return true if an async request is pending and recording start should be delayed + private boolean startBluetoothSco() { + if (!mBluetoothAdapter.isEnabled() || + mBluetoothHeadset == null || + mBluetoothHeadset.getConnectedDevices().size() == 0) { + return false; + } + + mBluetoothScoStarted = true; + if (mBluetoothScoState == SCO_AUDIO_STATE_CONNECTED) { + // already connected, good to go. + Toast.makeText(this, R.string.bt_using_headset, Toast.LENGTH_SHORT).show(); + return false; + } + + Log.i(TAG, "Starting Bluetooth SCO Mode"); + mAudioManager.startBluetoothSco(); + mRecordHandler.sendEmptyMessageDelayed( + BLUETOOTH_SCO_MODE_TIMEDOUT, BLUETOOTH_SCO_MODE_TIMEOUT_DUR); + + mBluetoothConnectingToast = Toast.makeText( + this, R.string.bt_headset_connecting, Toast.LENGTH_LONG); + mBluetoothConnectingToast.show(); + return true; + } + + private void onBluetoothScoStateChanged(int state) { + Log.v(TAG, "Bluetooth SCO state: " + mBluetoothScoState + " -> " + state); + switch (state) { + case SCO_AUDIO_STATE_DISCONNECTED: + if (mBluetoothScoState == SCO_AUDIO_STATE_CONNECTED && mBluetoothScoStarted) { + Toast.makeText( + this, R.string.bt_headset_disconnected, Toast.LENGTH_LONG).show(); + mRecordHandler.sendEmptyMessage(STOP); + } + break; + case SCO_AUDIO_STATE_CONNECTED: + if (mBluetoothScoStarted) { + Toast.makeText(this, R.string.bt_using_headset, Toast.LENGTH_SHORT).show(); + if (mBluetoothConnectingToast != null) { + mBluetoothConnectingToast.cancel(); + } + mRecordHandler.removeMessages(BLUETOOTH_SCO_MODE_TIMEDOUT); + mRecordHandler.obtainMessage(START_RECORDING_WITH_BT, + mBluetoothScoPendingRecordArg1, mBluetoothScoPendingRecordArg2) + .sendToTarget(); + } + break; + case SCO_AUDIO_STATE_CONNECTING: + break; + default: + Log.wtf(TAG, "Unexpected SCO_AUDIO_STATE: " + state); + break; + } + + mBluetoothScoState = state; + } + private void checkSRecPermissions() { String micPerm = Manifest.permission.RECORD_AUDIO; String storagePerm = Manifest.permission.WRITE_EXTERNAL_STORAGE; @@ -1630,6 +1765,9 @@ public class SoundRecorder extends Activity */ @Override public void onDestroy() { + + mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset); + if (mSDCardMountEventReceiver != null) { unregisterReceiver(mSDCardMountEventReceiver); mSDCardMountEventReceiver = null; |