summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKhalid Zubair <kzubair@cyngn.com>2016-06-30 12:25:15 -0700
committerRoman Birg <roman@cyngn.com>2016-06-30 15:48:39 -0700
commitc8b4cf4c6901ca7a675d9a43f9b2eb4b4d95065d (patch)
tree18e564d6c63eff04cb3c4e108b9f6e4d221b8206
parent02cf1e2913b73fd3de3976aaaf08f84601c69bec (diff)
downloadandroid_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.xml2
-rw-r--r--res/values/cm_strings.xml9
-rw-r--r--src/com/android/soundrecorder/SoundRecorder.java138
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;