1

I'm designing an application with Text-To-Speech and it works. Now I want to use the bluetooth headset (not AD2P) to receive audio. Reading Android documentation about startBluetoothScoOn I see:

Note that the phone application always has the priority on the usage of the SCO connection for telephony. If this method is called while the phone is in call it will be ignored. Similarly, if a call is received or sent while an application is using the SCO connection, the connection will be lost for the application and NOT returned automatically when the call ends.

Now my questions are:

  1. Do I need to listen for phone state to re-call startBluetoothScoOn when a call ends? If yes, how? I didn't find any example;
  2. Do I need to listen for headset connected/disconnected? If yes, how?

I suppose that when the calls end or my headset is turned off, the system delivers a SCO_AUDIO_STATE_DISCONNECTED, right?

Edit: I post code I'm using:

public class BluetoothHeadSetManager implements
    BluetoothProfile.ServiceListener {
BluetoothManager manager;
BluetoothHeadset bluetoothHeadset;
Context context;
HeadSetBroadcastReceiver recv;

public BluetoothHeadSetManager(Context cont, BluetoothManager manager) {
    this.manager = manager;
    this.context = cont;
    recv = null;
}

@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
    // mBluetoothHeadset is just a headset profile,
    // it does not represent a headset device.
    bluetoothHeadset = (BluetoothHeadset) proxy;

    List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
    if (devices.size() > 0)
        manager.onHeadSetConnected();

    // During the active life time of the app, a user may turn on and off
    // the headset.
    // So register for broadcast of connection states.
    if (recv == null) {
        recv = new HeadSetBroadcastReceiver();
        context.registerReceiver(recv, new IntentFilter(
                BluetoothDevice.ACTION_ACL_CONNECTED));
        context.registerReceiver(recv, new IntentFilter(
                BluetoothDevice.ACTION_ACL_DISCONNECTED));
    }
}

public void close() {
    if (recv != null)
        context.unregisterReceiver(recv);
    if (bluetoothHeadset != null)
        BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
                BluetoothProfile.HEADSET, bluetoothHeadset);
}

@Override
public void onServiceDisconnected(int arg0) {
    Logger.getInstance().writeLog("onServiceDisconnected called");
    if (recv != null)
        context.unregisterReceiver(recv);
    manager.onHeadSetDisconnected();
}

private class HeadSetBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        BluetoothDevice mConnectedHeadset;

        if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
            mConnectedHeadset = intent
                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            BluetoothClass bluetoothClass = mConnectedHeadset
                    .getBluetoothClass();
            if (bluetoothClass != null) {
                // Check if device is a headset. Besides the 2 below, are
                // there other
                // device classes also qualified as headset?
                int deviceClass = bluetoothClass.getDeviceClass();
                if (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE
                        || deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
                    // override this if you want to do other thing when the
                    // device is connected.
                    manager.onHeadSetConnected();
                }
            }
        } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
            manager.onHeadSetDisconnected();
        }
    }
}

Sco receiver:

public class BluetoothScoReceiver extends BroadcastReceiver {
BluetoothManager manager;

public BluetoothScoReceiver(BluetoothManager manager) {
    this.manager = manager;
}

@Override
public void onReceive(Context arg0, Intent intent) {
    Bundle extras = intent.getExtras();
    if (extras == null)
        return;
    int state = extras.getInt(AudioManager.EXTRA_SCO_AUDIO_STATE);
    if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED)
        manager.onScoConnected();
    else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED)
        manager.onScoDisconnected();
}

Main:

@Override
public void onScoConnected() {
    this.headsetReady = true;
    requestState = CONNECTED;
}

@Override
public void onScoDisconnected() {
    this.headsetReady = false;
    if (requestState == TRY_CONNECTION && headsetConnected)
        this.onHeadSetConnected(); //try again
}

@Override
public void onHeadSetConnected() {
    headsetConnected = true;
    if (requestState == TRY_CONNECTION) {
        requestState = TRY_CONNECTION;
        audioMng.setMode(AudioManager.MODE_NORMAL);
        audioMng.stopBluetoothSco();
        audioMng.setMode(AudioManager.MODE_IN_CALL);
        audioMng.startBluetoothSco();
    } else {
        requestState = TRY_CONNECTION;
        audioMng.setMode(AudioManager.MODE_IN_CALL);
        audioMng.startBluetoothSco();
    }
}

@Override
public void onHeadSetDisconnected() {
    audioMng.setMode(AudioManager.MODE_NORMAL);
    if (requestState != NOT_CONNECTED_IDLE) {
        audioMng.stopBluetoothSco();
        audioMng.setBluetoothScoOn(false);
        requestState = NOT_CONNECTED_IDLE;
    }
    headsetConnected = false;
}

@SuppressLint("InlinedApi")
@SuppressWarnings("deprecation")
@Override
public void startBluetooth() {
    Intent currentScoState;
    if (audioMng.isBluetoothScoAvailableOffCall()) {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null)
            return;
        blueInit = adapter.getProfileProxy(this, headsetMng,
                BluetoothProfile.HEADSET);
        if (blueInit) {
            scoListner = new BluetoothScoReceiver(this);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
                currentScoState = this
                        .registerReceiver(
                                scoListner,
                                new IntentFilter(
                                        AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
            else
                currentScoState = this
                        .registerReceiver(
                                scoListner,
                                new IntentFilter(
                                        AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
            Bundle extras = currentScoState.getExtras();
            if (extras == null)
                return;
            if (extras
                    .getInt(AudioManager.EXTRA_SCO_AUDIO_STATE) == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
                this.headsetReady = true;
                requestState = CONNECTED;
            } else {
                this.headsetReady = false;
                requestState = NOT_CONNECTED_IDLE;
            }
        }
    }
}

@Override
public void stopBluetooth() {
    this.onHeadSetDisconnected();
    if (blueInit) {
        headsetMng.close();
        this.unregisterReceiver(scoListner);
    }
}

1 Answers1

0
  1. You do not necessarily have to listen for phone states, when the call ends (CALL_STATE_IDLE) you will receive a SCO_AUDIO_STATE_DISCONNECTED.
  2. If you do not listen for headset connected how do you know when to connect the audio? For an bluetooth headset implementation you can look at Using the Android RecognizerIntent with a bluetooth headset
Community
  • 1
  • 1
Hoan Nguyen
  • 18,033
  • 3
  • 50
  • 54
  • In this example startBluetoothScoOn is not called at all, so I think I can use as inspiration only for point 2. However I really don't understande the difference between onServiceConnected/onServiceDisconnected and the receiver of the intent ACTION_CONNECTION_STATE_CHANGED are the same thing. Indeed they both call onHeadsetConnected(). Or onServiceConnected/onServiceDisconnected are not relative the headset connection but with an other aspect. – user1633717 Aug 20 '13 at 19:38
  • What example are you talking about? if you refer to the link I provided then startBluetoothSco() is called in the mBroadcastReceiver and there are no onServiceConnected/onServiceDisconnected anywhere in the codes so I do not know what do you refer to. – Hoan Nguyen Aug 20 '13 at 19:51
  • I'm using API >= 11, I'm talking about the class BluetoothHeadsetUtils – user1633717 Aug 21 '13 at 09:19
  • I tried a little, but it only works if the headset is already connected before my app starts. What I see is that ACTION_CONNECTION_STATE_CHANGED is never called, so I'm using ACTION_ACL_CONNECTED/ACTION_ACL_DISCONNECTED to know when an headset is connected and it works. But I don't understand why startBluetoothSco works (even if after a while) but only when the device is already connected. Any tips? – user1633717 Aug 21 '13 at 17:17
  • Look at my code in the link, I have to set a countdown timer and repeatedly call startBluetoothSco. – Hoan Nguyen Aug 21 '13 at 18:09
  • Yep, I saw. I'll try but if you see my code, when the sco receiver tell me DISCONNECTED, I call again startBluetoothSco, so instead to use a countdown, I trigger the call of startBluetoothSco when there is a transaction CONNECTING -> DISCONNECTED. – user1633717 Aug 22 '13 at 06:50
  • I tried but your code doesn't work at all, at least on my phone jb 4.2. Count down always fails. – user1633717 Aug 22 '13 at 10:25