Ошибка AudioManager.startBluetoothSco() на Android Lollipop

При обращении к AudioManager.startBluetoothSCO() при таргетинге API уровня 18 или выше в манифесте документация гласит, что необработанное аудио соединение установлено, и если используется таргетинг на API 17 или ниже виртуального голосового вызова.

До уровня API 20 (Android L Preview) это работало отлично, ориентируясь на любой API. Тем не менее, при использовании последней версии Lollipop для Android LXL13D и ориентированного на уровень API уровня 18 или выше я получаю сбой со следующей трассировкой стека:

E/AndroidRuntime (31705): вызвано: java.lang.NullPointerException: попытка вызвать виртуальный метод 'java.lang.String android.bluetooth.BluetoothDevice.getAddress()' для ссылки на нулевой объект 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): на android.media.IAudioService $Stub $Proxy.startBluetoothSco(IAudioService.java:1587) E/AndroidRuntime (31705): на android.media.AudioManager.startBluetoothSco(AudioManager.java:1468)

Если я нацелен на уровень API 17 или ниже на Android Lollipop, все работает так, как ожидалось.

Я считаю, что источником проблемы является изменение аудио-кода Android, которое произошло на уровне API 21 в файле AudioService.java, строка 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);
}

Похоже, SCO_MODE_UNDEFINED должен быть SCO_MODE_RAW. Если вы просмотрите файл, вы увидите, что SCO_MODE_RAW проверяется в нескольких местах, но никогда не передается нигде.

Кто-нибудь еще переживает этот крах? Кто-нибудь знает, что лучше исправить, чем понизить целевой SDK до 17? Если нет, можете ли вы, пожалуйста, опубликовать отчет об ошибке , который я подал в Google, чтобы увеличить вероятность того, что он будет выглядеть: -)

Ответ 1

Как писал @xsveda, если нет подключенной гарнитуры, вы получите NPE на Lollipop.

Сначала вы можете проверить подключение Bluetooth-гарнитуры:

mAudioManager.isWiredHeadsetOn()

Как описано в doc isWiredHeadsetOn() (ссылка doc) лишается и используется только для проверки подключения минигарнитуры или нет.

И после этого вы можете использовать соединение startBluetoothSco(). Что касается меня, я использовал этот код:

Этот для начала:

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

Этот для остановки:

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

Надеюсь, что это поможет.

Ответ 2

После нескольких дней отчаяния я нашел простой способ:

startBluetoothSco() выбрасывает NPE только в том случае, если нет подключенного устройства Bluetooth, поэтому его можно поймать и проигнорировать, так как "никто не разговаривает". Если подключена BT-гарнитура, SCO успешно запущен и воспроизведение работает!

Ответ 3

В настоящее время, похоже, для меня работает игнорирование 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, это работает для меня и прокладывает через микрофон Bluetooth, убедитесь, что устройство Bluetooth распознано и подключено:

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));
}

Надеюсь, это поможет кому-то!