Add working bluetooth support
This commit introduces basic bluetooth support.
If a headset is paired to the phone, audio will be correctly rerouted.
This is still experimental, due to the lack of testing devices.
Refs #41293
diff --git a/src/org/sflphone/utils/MediaManager.java b/src/org/sflphone/utils/MediaManager.java
index 30e2662..25ddef5 100644
--- a/src/org/sflphone/utils/MediaManager.java
+++ b/src/org/sflphone/utils/MediaManager.java
@@ -38,14 +38,17 @@
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.os.Handler;
import android.util.Log;
+import org.sflphone.utils.bluetooth.BluetoothWrapper;
-public class MediaManager implements OnAudioFocusChangeListener {
+public class MediaManager implements OnAudioFocusChangeListener, BluetoothWrapper.BluetoothChangeListener {
private static final String TAG = MediaManager.class.getSimpleName();
private SipService mService;
private SettingsContentObserver mSettingsContentObserver;
AudioManager mAudioManager;
private Ringer ringer;
+ //Bluetooth related
+ private BluetoothWrapper bluetoothWrapper;
public MediaManager(SipService aService) {
mService = aService;
@@ -56,6 +59,11 @@
}
public void startService() {
+ if(bluetoothWrapper == null) {
+ bluetoothWrapper = BluetoothWrapper.getInstance(mService);
+ bluetoothWrapper.setBluetoothChangeListener(this);
+ bluetoothWrapper.register();
+ }
mService.getApplicationContext().getContentResolver()
.registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver);
}
@@ -63,6 +71,11 @@
public void stopService() {
Log.i(TAG, "Remove media manager....");
mService.getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
+ if(bluetoothWrapper != null) {
+ bluetoothWrapper.unregister();
+ bluetoothWrapper.setBluetoothChangeListener(null);
+ bluetoothWrapper = null;
+ }
}
public AudioManager getAudioManager() {
@@ -72,7 +85,10 @@
public void obtainAudioFocus(boolean requestSpeakerOn) {
mAudioManager.requestAudioFocus(this, Compatibility.getInCallStream(false), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
- if(requestSpeakerOn && !mAudioManager.isWiredHeadsetOn()){
+ if(bluetoothWrapper != null && bluetoothWrapper.canBluetooth()) {
+ Log.d(TAG, "Try to enable bluetooth");
+ bluetoothWrapper.setBluetoothOn(true);
+ } else if (requestSpeakerOn && !mAudioManager.isWiredHeadsetOn()){
RouteToSpeaker();
}
}
@@ -123,4 +139,11 @@
ringer.stopRing();
}
}
+
+ @Override
+ public void onBluetoothStateChanged(int status) {
+ //setSoftwareVolume();
+ //broadcastMediaChanged();
+ }
+
}
diff --git a/src/org/sflphone/utils/bluetooth/BluetoothUtils14.java b/src/org/sflphone/utils/bluetooth/BluetoothUtils14.java
new file mode 100644
index 0000000..d7d681c
--- /dev/null
+++ b/src/org/sflphone/utils/bluetooth/BluetoothUtils14.java
@@ -0,0 +1,170 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * This file is part of CSipSimple.
+ *
+ * CSipSimple is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * If you own a pjsip commercial license you can also redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public License
+ * as an android library.
+ *
+ * CSipSimple is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CSipSimple. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sflphone.utils.bluetooth;
+
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.util.Set;
+
+public class BluetoothUtils14 extends BluetoothWrapper {
+
+ private static String TAG = BluetoothUtils14.class.getSimpleName();
+ private AudioManager audioManager;
+ private boolean isBluetoothConnected = false;
+
+
+ @Override
+ public boolean isBTHeadsetConnected() {
+ if(bluetoothAdapter != null) {
+ return (bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) == BluetoothAdapter.STATE_CONNECTED);
+ }
+ return false;
+ }
+
+
+
+ private BroadcastReceiver mediaStateReceiver = new BroadcastReceiver() {
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.d(TAG, ">>> BT SCO state changed !!! ");
+ if(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(action)) {
+ int status = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR );
+ Log.d(TAG, "BT SCO state changed : " + status + " target is " + targetBt);
+ audioManager.setBluetoothScoOn(targetBt);
+
+ if(status == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
+ isBluetoothConnected = true;
+ }else if(status == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
+ isBluetoothConnected = false;
+ }
+
+ if(btChangesListener != null) {
+ btChangesListener.onBluetoothStateChanged(status);
+ }
+ }
+ }
+ };
+
+ protected BluetoothAdapter bluetoothAdapter;
+
+ @Override
+ public void setContext(Context aContext){
+ super.setContext(aContext);
+ audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ if(bluetoothAdapter == null) {
+ try {
+ bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ }catch(RuntimeException e) {
+ Log.w(TAG, "Cant get default bluetooth adapter ", e);
+ }
+ }
+ }
+
+ public boolean canBluetooth() {
+ // Detect if any bluetooth a device is available for call
+ if (bluetoothAdapter == null) {
+ // Device does not support Bluetooth
+ return false;
+ }
+ boolean hasConnectedDevice = false;
+ //If bluetooth is on
+ if(bluetoothAdapter.isEnabled()) {
+
+ //We get all bounded bluetooth devices
+ // bounded is not enough, should search for connected devices....
+ Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
+ for(BluetoothDevice device : pairedDevices) {
+ BluetoothClass bluetoothClass = device.getBluetoothClass();
+ if (bluetoothClass != null) {
+ int deviceClass = bluetoothClass.getDeviceClass();
+ if(bluetoothClass.hasService(BluetoothClass.Service.RENDER) ||
+ deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET ||
+ deviceClass == BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO ||
+ deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ) {
+ //And if any can be used as a audio handset
+ hasConnectedDevice = true;
+ break;
+ }
+ }
+ }
+ }
+ boolean retVal = hasConnectedDevice && audioManager.isBluetoothScoAvailableOffCall();
+ Log.d(TAG, "Can I do BT ? "+retVal);
+ return retVal;
+ }
+
+ private boolean targetBt = false;
+ public void setBluetoothOn(boolean on) {
+ Log.d(TAG, "Ask for "+on+" vs "+audioManager.isBluetoothScoOn());
+ targetBt = on;
+ if(on != isBluetoothConnected) {
+ // BT SCO connection state is different from required activation
+ if(on) {
+ // First we try to connect
+ Log.d(TAG, "BT SCO on >>>");
+ audioManager.startBluetoothSco();
+ }else {
+ Log.d(TAG, "BT SCO off >>>");
+ // We stop to use BT SCO
+ audioManager.setBluetoothScoOn(false);
+ // And we stop BT SCO connection
+ audioManager.stopBluetoothSco();
+ }
+ }else if(on != audioManager.isBluetoothScoOn()) {
+ // BT SCO is already in desired connection state
+ // we only have to use it
+ audioManager.setBluetoothScoOn(on);
+ }
+ }
+
+ public boolean isBluetoothOn() {
+ return isBluetoothConnected;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void register() {
+ Log.d(TAG, "Register BT media receiver");
+ context.registerReceiver(mediaStateReceiver , new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
+ }
+
+ public void unregister() {
+ try {
+ Log.d(TAG, "Unregister BT media receiver");
+ context.unregisterReceiver(mediaStateReceiver);
+ }catch(Exception e) {
+ Log.w(TAG, "Failed to unregister media state receiver",e);
+ }
+ }
+}
diff --git a/src/org/sflphone/utils/bluetooth/BluetoothWrapper.java b/src/org/sflphone/utils/bluetooth/BluetoothWrapper.java
new file mode 100644
index 0000000..90478b4
--- /dev/null
+++ b/src/org/sflphone/utils/bluetooth/BluetoothWrapper.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * This file is part of CSipSimple.
+ *
+ * CSipSimple is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * If you own a pjsip commercial license you can also redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public License
+ * as an android library.
+ *
+ * CSipSimple is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CSipSimple. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sflphone.utils.bluetooth;
+
+import android.content.Context;
+
+
+public abstract class BluetoothWrapper {
+
+ public interface BluetoothChangeListener {
+ void onBluetoothStateChanged(int status);
+ }
+
+
+ private static BluetoothWrapper instance;
+ protected Context context;
+
+ protected BluetoothChangeListener btChangesListener;
+
+ public static BluetoothWrapper getInstance(Context context) {
+ if(instance == null) {
+ instance = new BluetoothUtils14();
+ if(instance != null) {
+ instance.setContext(context);
+ }
+ }
+
+ return instance;
+ }
+
+ protected BluetoothWrapper() {}
+
+ protected void setContext(Context ctxt) {
+ context = ctxt;
+ }
+
+ public void setBluetoothChangeListener(BluetoothChangeListener l) {
+ btChangesListener = l;
+ }
+
+ public abstract boolean canBluetooth();
+ public abstract void setBluetoothOn(boolean on);
+ public abstract boolean isBluetoothOn();
+ public abstract void register();
+ public abstract void unregister();
+ public abstract boolean isBTHeadsetConnected();
+}