Reworked Bluetooth manager for Android 3.0+, working better now

This commit is contained in:
Sylvain Berfini 2014-03-25 14:34:16 +01:00
parent 734c3b7625
commit d73ff36208
3 changed files with 279 additions and 174 deletions

View file

@ -17,13 +17,22 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
import java.util.List;
import org.linphone.LinphoneSimpleListener.LinphoneOnAudioChangedListener.AudioState;
import org.linphone.compatibility.Compatibility;
import org.linphone.mediastream.Log;
import org.linphone.mediastream.Version;
import android.annotation.TargetApi;
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.media.AudioManager;
import android.os.Build;
@ -32,50 +41,248 @@ import android.os.Build;
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class BluetoothManager extends BroadcastReceiver {
private static BluetoothManager instance;
private Context mContext;
private AudioManager mAudioManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothHeadset mBluetoothHeadset;
private BluetoothDevice mBluetoothDevice;
private BluetoothProfile.ServiceListener mProfileListener;
private BroadcastReceiver bluetoothActionReceiver = new BluetoothActionReceiver();
private boolean isBluetoothConnected;
private boolean isUsingBluetoothAudioRoute;
public static BluetoothManager getInstance() {
if (instance == null) {
instance = new BluetoothManager();
}
return instance;
}
/**
* Do not call !
*/
public BluetoothManager() {
isBluetoothConnected = false;
isUsingBluetoothAudioRoute = false;
try {
mContext = LinphoneManager.getInstance().getContext();
mAudioManager = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE));
instance = this;
} catch (Exception e) {}
}
public void startBluetooth() {
if (isBluetoothConnected) {
Log.e("Bluetooth already started");
return;
}
IntentFilter filter = new IntentFilter();
filter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
filter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED");
filter.addAction("android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED");
mContext.registerReceiver(this, filter);
Log.d("Bluetooth receiver started");
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter.isEnabled()) {
if (Version.sdkAboveOrEqual(Version.API11_HONEYCOMB_30)) {
if (mProfileListener != null) {
Log.w("Bluetooth headset profile was already opened, let's close it");
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
}
mProfileListener = new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
Log.d("Bluetooth headset connected");
mContext.registerReceiver(bluetoothActionReceiver, new IntentFilter(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT));
mBluetoothHeadset = (BluetoothHeadset) proxy;
isBluetoothConnected = true;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mContext.unregisterReceiver(bluetoothActionReceiver);
mBluetoothHeadset = null;
isBluetoothConnected = false;
Log.d("Bluetooth headset disconnected");
LinphoneManager.getInstance().routeAudioToSpeaker();
}
}
};
boolean success = mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
if (!success) {
Log.e("Bluetooth getProfileProxy failed !");
}
}
} else {
Log.w("Bluetooth interface disabled on device");
}
}
public boolean routeAudioToBluetooth() {
return routeAudioToBluetooth(false);
}
private boolean routeAudioToBluetooth(boolean isRetry) {
if (mBluetoothAdapter == null) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
if (mBluetoothAdapter.isEnabled() && mAudioManager.isBluetoothScoAvailableOffCall()) {
isUsingBluetoothAudioRoute = isBluetoothHeadsetAvailable();
if (isUsingBluetoothAudioRoute) {
if (mAudioManager != null) {
mAudioManager.setBluetoothScoOn(true);
mAudioManager.startBluetoothSco();
LinphoneManager.getInstance().audioStateChanged(AudioState.BLUETOOTH);
}
} else {
LinphoneManager.getInstance().audioStateChanged(AudioState.SPEAKER);
}
isUsingBluetoothAudioRoute = mBluetoothHeadset.isAudioConnected(mBluetoothDevice);
if (!isUsingBluetoothAudioRoute && !isRetry) {
Log.w("Routing audio to bluetooth headset failed, retry....");
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
return routeAudioToBluetooth(true);
} else if (isRetry) {
if (isUsingBluetoothAudioRoute) {
Log.d("Retry worked, audio is routed to bluetooth headset");
} else {
Log.e("Retry not worked, audio isn't routed to bluetooth headset...");
disableBluetoothSCO();
}
} else {
Log.d("Routing audio to bluetooth headset worked at first try");
}
return isUsingBluetoothAudioRoute;
}
return false;
}
public boolean isUsingBluetoothAudioRoute() {
return mBluetoothHeadset.isAudioConnected(mBluetoothDevice);
}
public boolean isBluetoothHeadsetAvailable() {
if (mBluetoothAdapter == null) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
if (mBluetoothAdapter.isEnabled() && mAudioManager.isBluetoothScoAvailableOffCall()) {
if (Version.sdkAboveOrEqual(Version.API11_HONEYCOMB_30)) {
boolean isHeadsetConnected = false;
if (mBluetoothHeadset != null) {
List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
for (final BluetoothDevice dev : devices) {
if (mBluetoothHeadset.getConnectionState(dev) == BluetoothHeadset.STATE_CONNECTED) {
mBluetoothDevice = dev;
isHeadsetConnected = true;
break;
}
}
Log.d(isHeadsetConnected ? "Headset found, bluetooth audio route available" : "No headset found, bluetooth audio route unavailable");
}
return isHeadsetConnected;
}
}
return false;
}
public void disableBluetoothSCO() {
isUsingBluetoothAudioRoute = false;
if (mAudioManager != null) {
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
Log.w("Bluetooth sco disconnected!");
}
}
public void stopBluetooth() {
Log.w("Stopping bluetooth...");
isBluetoothConnected = false;
isUsingBluetoothAudioRoute = false;
if (mAudioManager != null) {
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
}
if (mProfileListener != null && mBluetoothHeadset != null) {
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
mContext.unregisterReceiver(bluetoothActionReceiver);
mProfileListener = null;
mBluetoothHeadset = null;
}
mBluetoothDevice = null;
Log.w("Bluetooth stopped!");
if (LinphoneManager.getLc().getCallsNb() > 0) {
Log.w("Bluetooth disabled, Going back to incall mode");
Compatibility.setAudioManagerInCallMode(mAudioManager);
}
try {
mContext.unregisterReceiver(this);
Log.d("Bluetooth receiver stopped");
} catch (Exception e) {}
if (LinphoneManager.isInstanciated()) {
LinphoneManager.getInstance().routeAudioToSpeaker();
}
}
public void destroy() {
try {
stopBluetooth();
} catch (Exception e) {
e.printStackTrace();
}
}
@SuppressWarnings("deprecation")
public void onReceive(Context context, Intent intent) {
if (!LinphoneManager.isInstanciated())
return;
String action = intent.getAction();
LinphoneManager lm = LinphoneManager.getInstance();
if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
Log.e("Bluetooth Received Event" + " ACTION_ACL_DISCONNECTED" );
if (lm != null) {
lm.scoDisconnected();
lm.routeAudioToReceiver();
}
Log.d("Bluetooth Received Event" + " ACTION_ACL_DISCONNECTED");
stopBluetooth();
}
else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
Log.e("Bluetooth Received Event" + " ACTION_ACL_CONNECTED" );
if (lm != null) {
lm.scoConnected();
}
Log.d("Bluetooth Received Event" + " ACTION_ACL_CONNECTED");
startBluetooth();
}
else if (AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, 0);
Log.e("Bluetooth sco state changed : " + state);
Log.d("Bluetooth sco state changed : " + state);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
if (lm != null) {
lm.scoConnected();
}
startBluetooth();
} else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
if (lm != null) {
lm.scoDisconnected();
lm.routeAudioToReceiver();
}
stopBluetooth();
}
}
//Using real value instead of constant because not available before sdk 11
else if ("android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED".equals(action)) { //BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED
int currentConnState = intent.getIntExtra("android.bluetooth.adapter.extra.CONNECTION_STATE", //BluetoothAdapter.EXTRA_CONNECTION_STATE
0); //BluetoothAdapter.STATE_DISCONNECTED
Log.e("Bluetooth state changed: " + currentConnState);
if (lm != null && currentConnState == 2) { //BluetoothAdapter.STATE_CONNECTED
lm.startBluetooth();
Log.d("Bluetooth state changed: " + currentConnState);
if (currentConnState == 2) { //BluetoothAdapter.STATE_CONNECTED
startBluetooth();
}
}
}

View file

@ -247,7 +247,7 @@ public class InCallActivity extends FragmentActivity implements
slideOutTopToBottom = AnimationUtils.loadAnimation(this, R.anim.slide_out_top_to_bottom);
}
if (LinphoneManager.getInstance().isBluetoothScoConnected) {
if (BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) {
try {
if (routeLayout != null)
routeLayout.setVisibility(View.VISIBLE);
@ -292,7 +292,7 @@ public class InCallActivity extends FragmentActivity implements
} else {
speaker.setBackgroundResource(R.drawable.speaker_off);
routeSpeaker.setBackgroundResource(R.drawable.route_speaker_off);
if (LinphoneManager.getInstance().isUsingBluetoothAudioRoute) {
if (BluetoothManager.getInstance().isUsingBluetoothAudioRoute()) {
routeReceiver.setBackgroundResource(R.drawable.route_receiver_off);
routeBluetooth.setBackgroundResource(R.drawable.route_bluetooth_on);
} else {
@ -404,12 +404,13 @@ public class InCallActivity extends FragmentActivity implements
hideOrDisplayAudioRoutes();
}
else if (id == R.id.routeBluetooth) {
LinphoneManager.getInstance().routeAudioToBluetooth();
isSpeakerEnabled = false;
routeBluetooth.setBackgroundResource(R.drawable.route_bluetooth_on);
routeReceiver.setBackgroundResource(R.drawable.route_receiver_off);
routeSpeaker.setBackgroundResource(R.drawable.route_speaker_off);
hideOrDisplayAudioRoutes();
if (BluetoothManager.getInstance().routeAudioToBluetooth()) {
isSpeakerEnabled = false;
routeBluetooth.setBackgroundResource(R.drawable.route_bluetooth_on);
routeReceiver.setBackgroundResource(R.drawable.route_receiver_off);
routeSpeaker.setBackgroundResource(R.drawable.route_speaker_off);
hideOrDisplayAudioRoutes();
}
}
else if (id == R.id.routeReceiver) {
LinphoneManager.getInstance().routeAudioToReceiver();
@ -493,9 +494,12 @@ public class InCallActivity extends FragmentActivity implements
}
private void showVideoView() {
isSpeakerEnabled = true;
LinphoneManager.getInstance().routeAudioToSpeaker();
speaker.setBackgroundResource(R.drawable.speaker_on);
if (!BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) {
Log.w("Bluetooth not available, using speaker");
LinphoneManager.getInstance().routeAudioToSpeaker();
isSpeakerEnabled = true;
speaker.setBackgroundResource(R.drawable.speaker_on);
}
video.setBackgroundResource(R.drawable.video_off);
LinphoneManager.stopProximitySensorForActivity(InCallActivity.this);
@ -547,6 +551,7 @@ public class InCallActivity extends FragmentActivity implements
speaker.setBackgroundResource(R.drawable.speaker_on);
LinphoneManager.getLc().enableSpeaker(isSpeakerEnabled);
} else {
Log.d("Toggle speaker off, routing back to earpiece");
LinphoneManager.getInstance().routeAudioToReceiver();
speaker.setBackgroundResource(R.drawable.speaker_off);
}

View file

@ -78,10 +78,6 @@ import org.linphone.mediastream.video.capture.hwconf.Hacks;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
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.ContentResolver;
import android.content.Context;
@ -143,17 +139,7 @@ public class LinphoneManager implements LinphoneCoreListener {
private int mLastNetworkType=-1;
private ConnectivityManager mConnectivityManager;
private Handler mHandler = new Handler();
private WakeLock mIncallWakeLock;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothHeadset mBluetoothHeadset;
private BluetoothProfile.ServiceListener mProfileListener;
private BroadcastReceiver bluetoothReiceiver = new BluetoothManager();
private BroadcastReceiver bluetoothActionReceiver = new BluetoothActionReceiver();
public boolean isBluetoothScoConnected;
public boolean isUsingBluetoothAudioRoute;
private boolean mBluetoothStarted;
public String wizardLoginViewDomain = null;
@ -208,13 +194,8 @@ public class LinphoneManager implements LinphoneCoreListener {
private BroadcastReceiver mKeepAliveReceiver = new KeepAliveReceiver();
private void routeAudioToSpeakerHelper(boolean speakerOn) {
isUsingBluetoothAudioRoute = false;
if (mAudioManager != null && mBluetoothStarted) {
//Compatibility.setAudioManagerInCallMode(mAudioManager);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mBluetoothStarted = false;
}
Log.w("Routing audio to " + (speakerOn ? "speaker" : "earpiece") + ", disabling bluetooth audio route");
BluetoothManager.getInstance().disableBluetoothSCO();
if (!speakerOn) {
mLc.enableSpeaker(false);
@ -222,10 +203,15 @@ public class LinphoneManager implements LinphoneCoreListener {
mLc.enableSpeaker(true);
}
audioStateChanged(speakerOn ? AudioState.SPEAKER : AudioState.EARPIECE);
}
public void audioStateChanged(AudioState state) {
for (LinphoneOnAudioChangedListener listener : getSimpleListeners(LinphoneOnAudioChangedListener.class)) {
listener.onAudioStateChanged(speakerOn ? AudioState.SPEAKER : AudioState.EARPIECE);
listener.onAudioStateChanged(state);
}
}
public void routeAudioToSpeaker() {
routeAudioToSpeakerHelper(true);
}
@ -240,109 +226,9 @@ public class LinphoneManager implements LinphoneCoreListener {
return userAgent.toString();
}
/**
*
*/
public void routeAudioToReceiver() {
routeAudioToSpeakerHelper(false);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@SuppressWarnings("deprecation")
public void startBluetooth() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter.isEnabled()) {
if (Version.sdkAboveOrEqual(Version.API11_HONEYCOMB_30)) {
mProfileListener = new BluetoothProfile.ServiceListener() {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
Log.d("Bluetooth headset connected");
mServiceContext.registerReceiver(bluetoothActionReceiver, new IntentFilter(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT));
mBluetoothHeadset = (BluetoothHeadset) proxy;
isBluetoothScoConnected = true;
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mServiceContext.unregisterReceiver(bluetoothActionReceiver);
mBluetoothHeadset = null;
isBluetoothScoConnected = false;
Log.d("Bluetooth headset disconnected");
routeAudioToReceiver();
}
}
};
mBluetoothAdapter.getProfileProxy(mServiceContext, mProfileListener, BluetoothProfile.HEADSET);
} else {
try {
mServiceContext.unregisterReceiver(bluetoothReiceiver);
} catch (Exception e) {}
Intent currentValue = mServiceContext.registerReceiver(bluetoothReiceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
int state = currentValue == null ? 0 : currentValue.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, 0);
if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
isBluetoothScoConnected = true;
}
}
} else {
isBluetoothScoConnected = false;
scoDisconnected();
routeAudioToReceiver();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public boolean routeAudioToBluetooth() {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter.isEnabled() && mAudioManager.isBluetoothScoAvailableOffCall()) {
mAudioManager.setBluetoothScoOn(true);
mAudioManager.startBluetoothSco();
mBluetoothStarted=true;
if (Version.sdkAboveOrEqual(Version.API11_HONEYCOMB_30)) {
isUsingBluetoothAudioRoute = false;
if (mBluetoothHeadset != null) {
List<BluetoothDevice> devices = mBluetoothHeadset.getConnectedDevices();
for (final BluetoothDevice dev : devices) {
isUsingBluetoothAudioRoute |= mBluetoothHeadset.getConnectionState(dev) == BluetoothHeadset.STATE_CONNECTED;
}
}
if (!isUsingBluetoothAudioRoute) {
Log.d("No bluetooth device available");
scoDisconnected();
} else {
//Why is this for:
//mAudioManager.setMode(AudioManager.MODE_IN_CALL);
for (LinphoneOnAudioChangedListener listener : getSimpleListeners(LinphoneOnAudioChangedListener.class)) {
listener.onAudioStateChanged(AudioState.SPEAKER);
}
}
}
return isUsingBluetoothAudioRoute;
}
return false;
}
public void scoConnected() {
Log.i("Bluetooth sco connected!");
isBluetoothScoConnected = true;
}
public void scoDisconnected() {
Log.w("Bluetooth sco disconnected!");
isUsingBluetoothAudioRoute = false;
isBluetoothScoConnected = false;
if (mAudioManager != null) {
//why is this for ?
//mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
}
}
public synchronized static final LinphoneManager createAndStart(Context c, LinphoneServiceListener listener) {
if (instance != null)
@ -654,7 +540,7 @@ public class LinphoneManager implements LinphoneCoreListener {
updateNetworkReachability();
startBluetooth();
BluetoothManager.getInstance().startBluetooth();
resetCameraFromPreferences();
}
@ -741,19 +627,7 @@ public class LinphoneManager implements LinphoneCoreListener {
if (LinphoneService.isReady()) // indeed, no need to crash
ChatStorage.getInstance().close();
try {
mServiceContext.unregisterReceiver(bluetoothReiceiver);
} catch (Exception e) {}
try {
mServiceContext.unregisterReceiver(bluetoothActionReceiver);
} catch (Exception e) {}
try {
if (Version.sdkAboveOrEqual(Version.API11_HONEYCOMB_30))
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
} catch (Exception e) {}
BluetoothManager.getInstance().destroy();
try {
mTimer.cancel();
mLc.destroy();
@ -994,6 +868,7 @@ public class LinphoneManager implements LinphoneCoreListener {
}
if (Hacks.needSoftvolume()) {
Log.w("Using soft volume audio hack");
adjustVolume(0); // Synchronize
}
}
@ -1001,9 +876,9 @@ public class LinphoneManager implements LinphoneCoreListener {
if (state == State.CallReleased || state == State.Error) {
if (mLc.getCallsNb() == 0) {
if (mAudioFocused){
int res=mAudioManager.abandonAudioFocus(null);
int res = mAudioManager.abandonAudioFocus(null);
Log.d("Audio focus released a bit later: " + (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ? "Granted" : "Denied"));
mAudioFocused=false;
mAudioFocused = false;
}
Context activity = getContext();
@ -1027,7 +902,21 @@ public class LinphoneManager implements LinphoneCoreListener {
}
}
}
if (state == State.StreamsRunning) {
if (BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) {
BluetoothManager.getInstance().routeAudioToBluetooth();
// Hack for Android 4.2.2: we have to retry later, the first call will fail
if (Build.VERSION.SDK_INT == 17) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
BluetoothManager.getInstance().routeAudioToBluetooth();
}
}, 500);
}
}
if (mIncallWakeLock == null) {
mIncallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "incall");
}
@ -1148,7 +1037,10 @@ public class LinphoneManager implements LinphoneCoreListener {
isRinging = false;
// You may need to call galaxys audio hack after this method
routeAudioToReceiver();
if (!BluetoothManager.getInstance().isBluetoothHeadsetAvailable()) {
Log.d("Stopped ringing, routing back to earpiece");
routeAudioToReceiver();
}
}
@ -1360,6 +1252,7 @@ public class LinphoneManager implements LinphoneCoreListener {
if (activity != null) {
TelephonyManager tm = (TelephonyManager) activity.getSystemService(Context.TELEPHONY_SERVICE);
if (state == State.CallEnd && mLc.getCallsNb() == 0 && tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
Log.d("All call terminated, routing back to earpiece");
routeAudioToReceiver();
}
}