startBluetoothSco继续在SCO_AUDIO_STATE_CONNECTING和SCO_AUDIO_STATE_DISCONNECTED之间跳转

时间:2016-11-03 22:04:33

标签: java android bluetooth

所以我正在构建这个应用程序以支持蓝牙耳机。所以这是我的代码:

的AndroidManifest.xml

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private int activitiesCount;
    private BluetoothControllerImpl bluetoothController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bluetoothController = new BluetoothControllerImpl(this);
    }

    @Override
    protected void onPause() {
        onActivityPaused();
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        onActivityResume();
    }

    protected void onActivityResume() {
        if (activitiesCount++ == 0) { // on become foreground
            bluetoothController.start();
        }
    }

    protected void onActivityPaused() {
        if (--activitiesCount == 0) { // on become background
            bluetoothController.stop();
        }
    }

    private boolean isInForeground() {
        return activitiesCount > 0;
    }

    private class BluetoothControllerImpl extends BluetoothController {

        public BluetoothControllerImpl(Context context) {
            super(context);
        }

        @Override
        public void onHeadsetDisconnected() {
            Log.d(TAG, "Bluetooth headset disconnected");
        }

        @Override
        public void onHeadsetConnected() {
            Log.d(TAG, "Bluetooth headset connected");

            if (isInForeground() && !bluetoothController.isOnHeadsetSco()) {
                bluetoothController.start();
            }
        }

        @Override
        public void onScoAudioDisconnected() {
            Log.d(TAG, "Bluetooth sco audio finished");
            bluetoothController.stop();

            if (isInForeground()) {
                bluetoothController.start();
            }
        }

        @Override
        public void onScoAudioConnected() {
            Log.d(TAG, "Bluetooth sco audio started");
        }
    }
}

BluetoothController.java

public abstract class BluetoothController {

    private Context mContext;

    private BluetoothAdapter mBluetoothAdapter;

    private AudioManager mAudioManager;

    private boolean mIsCountDownOn;
    private boolean mIsStarting;
    private boolean mIsOnHeadsetSco;
    private boolean mIsStarted;

    private static final String TAG = "BluetoothController";

    /**
     * Constructor
     *
     * @param context
     */
    public BluetoothController(Context context) {
        mContext = context;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    }

    /**
     * Call this to start BluetoothController functionalities.
     *
     * @return The return value of startBluetooth()
     */
    public boolean start() {
        if (!mIsStarted) {
            mIsStarted = true;

            mIsStarted = startBluetooth();
        }

        return mIsStarted;
    }

    /**
     * Should call this on onResume or onDestroy.
     * Unregister broadcast receivers and stop Sco audio connection
     * and cancel count down.
     */
    public void stop() {
        if (mIsStarted) {
            mIsStarted = false;

            stopBluetooth();
        }
    }

    /**
     * @return true if audio is connected through headset.
     */
    public boolean isOnHeadsetSco() {
        return mIsOnHeadsetSco;
    }

    public abstract void onHeadsetDisconnected();

    public abstract void onHeadsetConnected();

    public abstract void onScoAudioDisconnected();

    public abstract void onScoAudioConnected();

    /**
     * Register for bluetooth headset connection states and Sco audio states.
     * Try to connect to bluetooth headset audio by calling startBluetoothSco().
     * This is a work around for API < 11 to detect if a headset is connected before
     * the application starts.
     * <p/>
     * The official documentation for startBluetoothSco() states
     * <p/>
     * "This method can be used by applications wanting to send and received audio to/from
     * a bluetooth SCO headset while the phone is not in call."
     * <p/>
     * Does this mean that startBluetoothSco() would fail if the connected bluetooth device
     * is not a headset?
     * <p/>
     * Thus if a call to startBluetoothSco() is successful, i.e mBroadcastReceiver will receive
     * an ACTION_SCO_AUDIO_STATE_CHANGED with intent extra SCO_AUDIO_STATE_CONNECTED, then
     * we assume that a headset is connected.
     *
     * @return false if device does not support bluetooth or current platform does not supports
     * use of SCO for off call.
     */
    @SuppressWarnings("deprecation")
    private boolean startBluetooth() {
        Log.d(TAG, "startBluetooth");

        // Device support bluetooth
        if (mBluetoothAdapter != null) {
            if (mAudioManager.isBluetoothScoAvailableOffCall()) {
                Log.d(TAG, "isBluetoothScoAvailableOffCall");
                mContext.registerReceiver(mBroadcastReceiver,
                        new IntentFilter(android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED));
                mContext.registerReceiver(mBroadcastReceiver,
                        new IntentFilter(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED));
                mContext.registerReceiver(mBroadcastReceiver,
                        new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));

                mAudioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
                        @Override
                        public void onAudioFocusChange(int focusChange) {
                            Log.d(TAG, "onAudioFocusChange focusChange=" + focusChange);
                        }
                    }, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);

                // Need to set audio mode to MODE_IN_CALL for call to startBluetoothSco() to succeed.
                mAudioManager.setMode(AudioManager.MODE_IN_CALL);

                mIsCountDownOn = true;
                // mCountDown repeatedly tries to start bluetooth Sco audio connection.
                mCountDown.start();

                // need for audio sco, see mBroadcastReceiver
                mIsStarting = true;

                return true;
            }
        }

        return false;
    }

    /**
     * Unregister broadcast receivers and stop Sco audio connection
     * and cancel count down.
     */
    private void stopBluetooth() {
        Log.d(TAG, "stopBluetooth");

        if (mIsCountDownOn) {
            mIsCountDownOn = false;
            mCountDown.cancel();
        }

        // Need to stop Sco audio connection here when the app
        // change orientation or close with headset still turns on.
        mContext.unregisterReceiver(mBroadcastReceiver);
        mAudioManager.stopBluetoothSco();
        mAudioManager.setMode(AudioManager.MODE_NORMAL);
    }

    /**
     * Handle headset and Sco audio connection states.
     */
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @SuppressWarnings({"deprecation", "synthetic-access"})
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (action.equals(android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED)) {
                Log.d(TAG, "ACTION_ACL_CONNECTED 1");
                android.bluetooth.BluetoothDevice mConnectedHeadset = intent.getParcelableExtra(android.bluetooth.BluetoothDevice.EXTRA_DEVICE);
                BluetoothClass bluetoothClass = mConnectedHeadset.getBluetoothClass();
                if (bluetoothClass != null) {
                    Log.d(TAG, "ACTION_ACL_CONNECTED 2");
                    // 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) {

                        Log.d(TAG, "ACTION_ACL_CONNECTED deviceClass=" + deviceClass);
                        // start bluetooth Sco audio connection.
                        // Calling startBluetoothSco() always returns faIL here,
                        // that why a count down timer is implemented to call
                        // startBluetoothSco() in the onTick.
                        mAudioManager.setMode(AudioManager.MODE_IN_CALL);
                        mIsCountDownOn = true;
                        mCountDown.start();

                        // override this if you want to do other thing when the device is connected.
                        onHeadsetConnected();
                    }
                }

                Log.d(TAG, mConnectedHeadset.getName() + " connected");
            } else if (action.equals(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                Log.d(TAG, "Headset disconnected");

                if (mIsCountDownOn) {
                    mIsCountDownOn = false;
                    mCountDown.cancel();
                }

                mAudioManager.setMode(AudioManager.MODE_NORMAL);

                // override this if you want to do other thing when the device is disconnected.
                onHeadsetDisconnected();
            } else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) {
                int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
                        AudioManager.SCO_AUDIO_STATE_ERROR);

                Log.d(TAG, "ACTION_SCO_AUDIO_STATE_CHANGED state=" + state);

                if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
                    mIsOnHeadsetSco = true;

                    if (mIsStarting) {
                        // When the device is connected before the application starts,
                        // ACTION_ACL_CONNECTED will not be received, so call onHeadsetConnected here
                        mIsStarting = false;
                        onHeadsetConnected();
                    }

                    if (mIsCountDownOn) {
                        mIsCountDownOn = false;
                        mCountDown.cancel();
                    }

                    // override this if you want to do other thing when Sco audio is connected.
                    onScoAudioConnected();

                    Log.d(TAG, "Sco connected");
                } else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
                    Log.d(TAG, "Sco disconnected");

                    // Always receive SCO_AUDIO_STATE_DISCONNECTED on call to startBluetooth()
                    // which at that stage we do not want to do anything. Thus the if condition.
                    if (!mIsStarting) {
                        mIsOnHeadsetSco = false;

                        // Need to call stopBluetoothSco(), otherwise startBluetoothSco()
                        // will not be successful.
                        mAudioManager.stopBluetoothSco();
                        mAudioManager.setBluetoothScoOn(false);
                        mAudioManager.setSpeakerphoneOn(true);

                        // override this if you want to do other thing when Sco audio is disconnected.
                        onScoAudioDisconnected();
                    }
                }
            }
        }
    };

    /**
     * Try to connect to audio headset in onTick.
     */
    private CountDownTimer mCountDown = new CountDownTimer(20000, 1000) {

        @SuppressWarnings("synthetic-access")
        @Override
        public void onTick(long millisUntilFinished) {
            // When this call is successful, this count down timer will be canceled.
            try {
                mAudioManager.setSpeakerphoneOn(false);
                mAudioManager.setBluetoothScoOn(true);
                mAudioManager.startBluetoothSco();
            } catch (Exception ignored) {

            }
            Log.d(TAG, "\nonTick start bluetooth Sco");
        }

        @SuppressWarnings("synthetic-access")
        @Override
        public void onFinish() {
            // Calls to startBluetoothSco() in onStick are not successful.
            // Should implement something to inform user of this failure
            mIsCountDownOn = false;
            mAudioManager.setMode(AudioManager.MODE_NORMAL);

            Log.d(TAG, "\nonFinish fail to connect to headset audio");
        }
    };
}

所以我确保通过播放YouTube视频和录制声音将蓝牙耳机(Bose无线耳机)连接到我的平板电脑(Acer运行Lollipop)。当我尝试运行上面的代码时,我得到以下输出:

BluetoothController: startBluetooth
BluetoothController: isBluetoothScoAvailableOffCall
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=2
BluetoothController: onTick start bluetooth Sco
BluetoothController: onTick start bluetooth Sco
BluetoothController: ACTION_SCO_AUDIO_STATE_CHANGED state=0
BluetoothController: Sco disconnected
BluetoothController: onFinish fail to connect to headset audio

我从未得到启动蓝牙Sco方法的成功。它总是在连接和断开之间跳跃。我做错了什么?

2 个答案:

答案 0 :(得分:0)

事实证明我使用的平板电脑在蓝牙设置中不支持手机音频。

因此,为了确保蓝牙连接正常工作,您需要进入设置 - >蓝牙,然后单击配对蓝牙耳机旁边的设置图标。您应该确保选中“电话音频”和“媒体音频”。如果您缺少“电话音频”,您将获得与我的连接断开相同的输出。

希望这有帮助!

答案 1 :(得分:0)

即使设备支持的蓝牙电话音频和设置正确,我也遇到同样的问题。

当我在接收到BluetoothHeadset.STATE_CONNECTED事件后立即调用startBluetoothSco()时,SCO状态从SCO_AUDIO_STATE_CONNECTING变为SCO_AUDIO_STATE_DISCONNECTED。通过在BluetoothHeadset.STATE_CONNECTED事件之后对startBluetoothSco()的调用引入1秒钟延迟来解决此问题:

BluetoothHeadset.STATE_CONNECTED -> {
    if (isAudioFocused()) {
        Timer("Sco", false).schedule(1000) {
            myAudioManager.startBluetoothSco()
        }
}