Home » Android » bluetooth – AudioManager.startBluetoothSco() crashes on Android Lollipop

bluetooth – AudioManager.startBluetoothSco() crashes on Android Lollipop

Posted by: admin May 14, 2020 Leave a comment

Questions:

When making a call to AudioManager.startBluetoothSCO() while targeting API level 18 or above in the Manifest, the documentation states that a raw audio connection is established, and if targeting API 17 or below a virtual voice call is used.

Up until API level 20 (Android L Preview) this worked fine, targeting any API. However, when using the latest Android Lollipop build LPX13D and targeting API level 18 or above I get a crash with the following stack trace:

E/AndroidRuntime(31705): Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ‘java.lang.String android.bluetooth.BluetoothDevice.getAddress()’ on a null object reference
E/AndroidRuntime(31705): at android.os.Parcel.readException(Parcel.java:1546)
E/AndroidRuntime(31705): at android.os.Parcel.readException(Parcel.java:1493)
E/AndroidRuntime(31705): at android.media.IAudioService$Stub$Proxy.startBluetoothSco(IAudioService.java:1587)
E/AndroidRuntime(31705): at android.media.AudioManager.startBluetoothSco(AudioManager.java:1468)

If I target API level 17 or below on Android Lollipop everything works as expected.

I believe the source of the problem lies in a change to Android’s audio code that happened in API level 21 in the file AudioService.java line 2392:

public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
    int scoAudioMode =
            (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
                    SCO_MODE_VIRTUAL_CALL : SCO_MODE_UNDEFINED;
    startBluetoothScoInt(cb, scoAudioMode);
}

It looks like SCO_MODE_UNDEFINED should instead be SCO_MODE_RAW. If you look through the file you can see that SCO_MODE_RAW is checked for in a few places, but is never actually passed in anywhere.

Is anybody else experiencing this crash? Does anyone know of a better fix than downgrading the target SDK to 17? If not, could you please star the bug report I filed with Google to increase the chance that it will be looked at 🙂

How to&Answers:

As @xsveda wrote if there is no headset connected, you will receive NPE on Lollipop.

You can try to check bluetooth headset connection first:

mAudioManager.isWiredHeadsetOn()

As doc described isWiredHeadsetOn() (doc link) is depricated and uses only to check is a headset is connected or not.

And after this you can use startBluetoothSco() connection. As for me I used this code:

This one for start:

if(mAudioManager.isWiredHeadsetOn())
    mAudioManager.startBluetoothSco();

This one for stop:

if(mAudioManager.isBluetoothScoOn())
            mAudioManager.stopBluetoothSco();

Hope it helps.

Answer:

After few days of despair I’ve found a simple workaround:

startBluetoothSco() throws NPE only if there is no Bluetooth device connected so it can be caught and ignored as there is “no one to talk to”. If i.e. a BT headset is connected, SCO is started successfully and playback is working!

Answer:

For now what seems to work for me was ignoring the NullPointerException:

private void tryConnectAudio() {
    verifyBluetoothSupport();
    try {
        mAudioManager.startBluetoothSco();
    } catch (NullPointerException e) {
        // TODO This is a temp workaround for Lollipop
        Log.d(TAG, "startBluetoothSco() failed. no bluetooth device connected.");
    }
}

@Julian Claudino, This works for me and routing through the Bluetooth mic, make sure that the Bluetooth device is recognized and connected:

private void verifyBluetoothSupport() {
    getActivity().registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
            Log.d(TAG, "Audio SCO state: " + state);
            if (AudioManager.SCO_AUDIO_STATE_CONNECTED == state) {
                Toast.makeText(getActivity(), "Bluetooth Connected", Toast.LENGTH_SHORT).show();
                getActivity().unregisterReceiver(this);
            }
        }
    }, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
}

Hope this helps someone !