Android Bluetooth development series - Bluetooth speaker connection

After a period of tossing and turning, my Android Studio can finally work normally, and the pits encountered during this period are recorded in the article< Create the pit encountered by the first project of Android Studio 3.5>.

We are in the " Android Bluetooth development series - Planning >Now the first part of a2dp: pairing and connecting of a2dp devices.

First of all, I'd like to introduce my little friend, a Bluetooth stereo with unknown brand, Huawei glory 7 mobile phone, and a ThinkPad T480 that cost me 9000 yuan.

Overview of Bluetooth speaker connection process: first, the mobile terminal needs to initiate scanning, after scanning to the device, it needs to identify the target device, that is, my Bluetooth speaker, and then initiate pairing for the Bluetooth speaker, and initiate the connection to the device after the pairing is successful.

That is to say, three fluencies are needed: (1) scanning, (2) pairing, (3) connection.

Generally speaking, the pairing and connection process is a consistent action in the user's scenario, that is, after the pairing is successful, the connection to the speaker will be automatically sent (the speaker also has only one prompt tone: Bluetooth pairing is successful).

1. Device scanning

Now, Android manifest.xml requests the following two permissions:

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

I have created 5 buttons to manually string related processes, which are used by play? PCM and play? Music users later.

<Button
        android:id="@+id/bt_scan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="scan device" />

    <Button
        android:id="@+id/bt_createbond"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="createbond" />

    <Button
        android:id="@+id/bt_connect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="connect" />

    <Button
        android:id="@+id/bt_playpcm"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="play_pcm" />

    <Button
        android:id="@+id/bt_playmusic"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="play_music" />

After the application is started, the first logic is as follows:

Initialize the broadcast receiver to connect the processes in series, which will be described one by one.

Initialize Bluetooth and get the Bluetooth adapter object.

Gets the proxy object to the Bluetooth A2DP service.

Scanning equipment

         //Initialize View
        initView();

        //Initialize broadcast receiver
        mBtReceiver = new BtReceiver();
        IntentFilter intent = new IntentFilter();
        intent.addAction(BluetoothDevice.ACTION_FOUND);
        intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        intent.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        registerReceiver(mBtReceiver, intent);
        //Initialize Bluetooth
        initBt();

        //Initialize profileProxy
        initProfileProxy();
        //Start scanning
        scanDevice();

First let's look at initBt():

Try to get the Bluetooth adapter object. If not, Bluetooth is not supported. At present, a more formal way is to see whether the device supports Bluetooth feature s. It can be judged by calling the PackageManager interface, but the effect is the same.

    private void initBt() {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        Log.d(TAG, "mBluetothAdapter = " + mBluetoothAdapter);
        if (mBluetoothAdapter == null) {
            Log.d(TAG, "do't support bt,return");
        }
    }

Take a look at initProfileProxy():

The purpose is to get the connection () method calling Bluetooth A2DP to connect to the speaker. I see many examples, even in the native settings, the device is retrieved after the device is paired successfully. This will cause a problem: if the following onServiceConnected() returns slowly, it will affect the later connection logic, that is, it needs to wait for a period of time to initiate a connection to the device.

In fact, this interface can be called after Bluetooth is turned on, which can save the waiting time mentioned above.

private int initProfileProxy() {
        mBluetoothAdapter.getProfileProxy(this,mProfileListener, BluetoothProfile.A2DP);
        return 0;
}

private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            Log.d(TAG, "onServiceConnected, profile = " + profile);
            if(profile == BluetoothProfile.A2DP) {
                mBluetoothA2dpProfile = (BluetoothA2dp)proxy;
            }
        }

        @Override
        public  void  onServiceDisconnected(int profile) {
            Log.d(TAG, "onServiceDisconnected, profile = " + profile);
        }
    };

Then, a Bluetooth scan can be initiated:

The premise of scanning is that the Bluetooth of the mobile phone is turned on. Therefore, we call the Bluetooth adapter:: isenable() method to determine whether the Bluetooth is turned on. If it is turned on, we call the Bluetooth adapter:: startdiscovery() method to initiate device scanning.

private void scanDevice() {
        if(opentBt()) {
            if (mBluetoothAdapter.isDiscovering()) {
                Log.d(TAG, "scanDevice is already run");
                return;
            }
            boolean ret = mBluetoothAdapter.startDiscovery();
            Log.d(TAG, "scanDevice ret = " + ret);
        } else {
            Log.d(TAG, "bt is not open,wait");
        }

    }
private boolean opentBt() {
        if(mBluetoothAdapter.isEnabled()) {
            Log.d(TAG, "bt is aleady on,return");
            return true;
        } else {
            mBluetoothAdapter.enable();
            mHandler.sendEmptyMessageAtTime(MSG_SCAN, DELAYT_TIMES);
            return false;
        }
    }

 

If Bluetooth is not turned on, we will turn on Bluetooth first, and then delay sending a message out, which will trigger the scanning logic again.

mHandler = new Handler(){
            @Override
            public void dispatchMessage(Message msg) {
                    Log.d(TAG, "dispatchMessage, msg.what = " + msg.what);
                    switch (msg.what) {
                        case MSG_SCAN:
                            scanDevice();
                            break;
                        case MSG_PAIR:
                            pairDevice();
                            break;
                        case MSG_CONNECT:
                            connectDevice();
                            break;
                        default:
                            break;
                    }
            }
        };

Here are two questions to answer:

First question: why call Bluetooth adapter:: startdiscovery() to initiate a scan instead of calling other interfaces?

Because our speaker device is a classic Bluetooth device, i.e. BR/EDR type device. There is also a Bluetooth device type called low-power Bluetooth device, i.e. BLE device.

The startDiscovery() interface can scan these two types of devices, while other interfaces can only scan BLE type devices.

I will write a separate article to summarize the differences between these device interfaces and how to quickly search for the target device.

Second question: why delay to call scan again instead of calling to turn on Bluetooth and then calling?

The reason is that Bluetooth needs to be opened for a period of time (under normal circumstances, it is also within 1s). Moreover, the Bluetooth protocol stack can only process one instruction in a period of time. If two interfaces are called continuously, problems will occur at the bottom of Bluetooth.

So for our application layer, how do we know the scanned device?  

The Bluetooth protocol stack reports the scanned Bluetooth devices to the framework layer through callback, and the framework layer will send Bluetooth deive.action "found" for broadcast. The application layer registers to receive the broadcast and obtains the device information from intent after receiving it.

            //onReceive() method
            final String action = intent.getAction();
            Log.d(TAG, "onReceive intent = " + action);
            if(action.equals(BluetoothDevice.ACTION_FOUND)) {
                BluetoothDevice btdevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                final String address = btdevice.getAddress();
                final String deviceName = btdevice.getName();
                Log.d(TAG, "onReceive found device, deivce address = " + address + ",deviceName = " + deviceName);
                if(isTargetDevice(btdevice)) {
                    stopScan();
                    mHandler.sendEmptyMessageDelayed(MSG_PAIR, DELAYT_TIMES);
                }
            }
After receiving the broadcast, we need to judge the searched device to see whether it is our target device. The way to judge the target equipment is to carry out a series of interpretation and eliminate the interference equipment. Here, I use the terms of equipment for judgment.
    //The target device can be set according to multiple restrictions, such as signal strength, device type, device name, etc.
    //Here we only use the device name to judge
    private boolean isTargetDevice(BluetoothDevice device) {
        if(device.getName() != null && device.getName().equals("S7")) {
            Log.d(TAG, "deivce :" + device.getName() + "is target device");
            mTargetDevice = device;
            return true;
        }
        Log.d(TAG, "deivce :" + device.getName() + "is not target device");
        return false;
    }

2. Equipment pairing:

The pairing of Bluetooth devices calls the Bluetooth device:: createbond() method,

    private void pairDevice() {
        Log.d(TAG,"start pair device = " + mTargetDevice);
        if(mTargetDevice.getBondState() != BluetoothDevice.BOND_NONE){
            Log.d(TAG, "targetdevice is already bonded,return");
            return;
        }
        mTargetDevice.createBond();
    }

The application layer registers and receives Bluetooth device.action? Bond? State? Changed broadcast, and judges the change of pairing state after receiving.

We get the Bluetooth device object from intent, that is, which device's pairing status has changed.

preBondState is the previous pairing state, and newBondState is the new state. A successful pairing process is as follows:

Bluetooth device. Bond? None (10) -- > Bluetooth device. Bond? Binding (11) -- > Bluetooth device. Bond? Bound (12). If other status changes, the pairing fails~

When receiving the change of pairing status of 11-12, it is considered that the device pairing is successful. We need to judge whether the successfully paired device is the target device. Only the target device (that is, the device we initiated the pairing) can carry out the next process: connection.

if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                BluetoothDevice btdevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int preBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR);
                int newBondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
                Log.d(TAG, "btdeivice = " + btdevice.getName() + "bond state change, preBondState = " + preBondState
                        + ", newBondState = " + newBondState);
                if(preBondState == BluetoothDevice.BOND_BONDING && newBondState == BluetoothDevice.BOND_BONDED) {
                    //Determine if it's the target device
                    if(isTargetDevice(btdevice)) {
                        connectDevice();
                    }

                }

3. Equipment Connection:

The connection of devices is through different profiles. A2DP devices need to be connected through A2DP profiles, and hid devices (such as mouse) need to be connected through input profile s.

I will write a separate article on how to distinguish device types.

    private void connectDevice() {
        if(mBluetoothA2dpProfile == null) {
            Log.d(TAG, "don't get a2dp profile,can not run connect");
        } else {
            try {
                //Get the connect method in Bluetooth A2DP by reflection
                Method connectMethod = BluetoothA2dp.class.getMethod("connect",
                        BluetoothDevice.class);
                Log.d(TAG, "connectMethod = " + connectMethod);
                connectMethod.invoke(mBluetoothA2dpProfile, mTargetDevice);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

Because Bluetooth A2DP's connect() method is hide's, you need to get the call through reflection. Of course, you can compile in the source code yourself, so you don't need reflection.

/**
209     * Initiate connection to a profile of the remote bluetooth device.
210     *
211     * <p> Currently, the system supports only 1 connection to the
212     * A2DP profile. The API will automatically disconnect connected
213     * devices before connecting.
214     *
215     * <p> This API returns false in scenarios like the profile on the
216     * device is already connected or Bluetooth is not turned on.
217     * When this API returns true, it is guaranteed that
218     * connection state intent for the profile will be broadcasted with
219     * the state. Users can get the connection state of the profile
220     * from this intent.
221     *
222     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
223     * permission.
224     *
225     * @param device Remote Bluetooth Device
226     * @return false on immediate error,
227     *               true otherwise
228     * @hide
229     */
230    public boolean connect(BluetoothDevice device) {
231        if (DBG) log("connect(" + device + ")");
232        if (mService != null && isEnabled() &&
233            isValidDevice(device)) {
234            try {
235                return mService.connect(device);
236            } catch (RemoteException e) {
237                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
238                return false;
239            }
240        }
241        if (mService == null) Log.w(TAG, "Proxy not attached to service");
242        return false;
243    }

The change of connection state can be realized by listening to bluetootha2dp.action? Connection? State? Changed. Preconnection state indicates the previous connection state and newConnectionState indicates the latter.

A normal connection state process is: Bluetooth profile. State? Disconnected (0) -- > Bluetooth profile. State? Connecting (1) -- >

BluetoothProfile.STATE_CONNCTED(2).

If there is a state change of 0 -- > 1 -- > 0, the connection fails. It needs to be analyzed according to the Bluetooth log.

A normal disconnection process is: Bluetooth profile. State? Connected (2) -- > Bluetooth profile. State? Disconnecting (3) -- > Bluetooth profile. State? Disconnected (0).

 if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
                BluetoothDevice btdevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int preConnectionState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
                int newConnectionState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
                Log.d(TAG, "btdevice = " + btdevice.getName() + ", preConnectionState = "
                        + preConnectionState + ", newConnectionState" + newConnectionState);
                if(newConnectionState == BluetoothProfile.STATE_CONNECTED && preConnectionState == BluetoothProfile.STATE_CONNECTING) {
                    Log.d(TAG, "target device connect success");
                }
            }

4. summary

This paper mainly analyzes the process of pairing and connecting of classic Bluetooth devices from the perspective of application layer. Roughly: scan the device -- listen to the device ﹣ found broadcast -- > until the target device is found -- > initiate pairing for the target device -- > listen to the device pairing success -- > initiate device connection -- > listen to the broadcast in connection status, and the connection is successful.

In the following articles, the following will be analyzed:

(1) How to distinguish equipment, i.e. equipment classification;

(2) How to scan equipment quickly;

If you want to keep your eyes on this blog content, scan the personal WeChat official account or WeChat scan: Internet of things technology.

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Published 29 original articles, won praise 12, visited 10000+
Private letter follow

Tags: Android Mobile xml

Posted on Sat, 07 Mar 2020 20:43:37 -0500 by lucasmontan