Android car application development and analysis (13) - system settings - bluetooth settings

1 Introduction

Android car application development and analysis is a series of articles, this is the 13th analysis system setting, this series of articles aims to analyze the implementation of core applications in the native car Android system, and help students who are engaged in car application development for the first time, better Understand the way of vehicle application development, and accumulate experience in the development of Android system applications.

2. Overview of system settings

System settings is a very important system-level application in the vehicle Android system. It is the control center of the entire vehicle IVI system. The sound effects, wireless communication, status information, safety information, etc. of the vehicle need to be viewed and controlled through the system settings. For example, turn on/off wifi and bluetooth, view the network traffic of each application, enable debugging information, etc.

Students with car experience should have seen the following system settings with weird font colors. This is actually how the system settings of the mobile phone are transplanted into the car system. When an in-vehicle Android project was launched, Mets opted to keep the more full-featured phone-native system settings instead of using the in-vehicle version.

The original system settings of the car are long

. In view of the many functions of the system settings, the source code of the system settings is also relatively complicated, and generally we will not follow the original code structure when writing the car system settings, so this article will not introduce the system settings. The source code structure and initialization process mainly focus on the application of the system API.

This time, we will start with the Bluetooth module.

3. Introduction to Bluetooth

Bluetooth ( Bluetooth ) is a wireless communication technology standard used to allow fixed and mobile devices to exchange data between short distances to form a personal area network (PAN). It uses short baud high frequency (UHF) radio waves to communicate via the ISM frequency band from 2.4 to 2.485 GHz. The technology was developed in 1994 by the telecommunications company Ericsson. It was originally designed to create a wireless communication replacement for the RS-232 data line. It is able to connect multiple devices, overcoming synchronization issues.

Bluetooth technology is currently maintained by the Bluetooth Special Interest Group (SIG), which has more than 30,000 members and is distributed in the fields of telecommunications, computers, networks and consumer electronics. IEEE once standardized Bluetooth technology as IEEE 802.15.1, but this standard is no longer in use.

3.1. Bluetooth classification

On July 7, 2010, the Bluetooth SIG launched the Bluetooth 4.0 specification. Bluetooth 4.0 includes three sub-standards, namely "Bluetooth Low Power", "Traditional Bluetooth" and "High-Speed ​​Bluetooth".

  • Bluetooth Low Energy

Bluetooth Low Energy ( Bluetooth Low Energy , or Bluetooth LE , BLE , the old trademark Bluetooth Smart ), also known as Bluetooth Low Energy , Bluetooth Low Energy , is a personal area network technology designed and sold by the Bluetooth Special Interest Group. Emerging applications in healthcare, sports and fitness, beacons, security, home entertainment, and more. Compared to classic Bluetooth, Bluetooth low energy is designed to significantly reduce power consumption and cost while maintaining the same communication range.

  • classic bluetooth

Classic bluetooth module, generally used for relatively large amount of transmission: such as voice, music and other high data volume transmission

  • high speed bluetooth

High-speed Bluetooth focuses on data exchange and transmission

3.2. Bluetooth Specification

Bluetooth specification (Bluetooth profile), the Bluetooth Special Interest Group defines many profiles. The purpose of Profile is to ensure interoperability between Bluetooth devices. However, Bluetooth products do not need to implement all Bluetooth specification profiles. Bluetooth version 1.1 defines 13 Profiles. The following are commonly used in Android:

PBAP protocol , Phone Book Access Profile (Phone Book Access Profile), is an upper-layer protocol based on OBEX, which can synchronize information such as address books and call records on mobile phones with phone book functions.

The HFP protocol , the Hands-Free Profile, the remote wireless control and voice connection between the mobile phone and the hands-free device is through the HFP protocol.

The A2DP protocol, the Bluetooth stereo audio transmission specification (Advance Audio Distribution Profile), specifies the protocol stack software and usage method for transmitting high-quality music file data using the Bluetooth asynchronous transmission channel method. stereo music. Divided into version 1.1 and version 1.2, as long as both sides of the connection support the A2DP protocol, the sound signal can be transmitted at 16 bits and 44.1 kHz quality. If one party does not support A2DP, it can only use the Handsfree Profile (Handsfree Profile) transmission mode with 8 bits and 8 kHz quality, and the sound quality will be greatly reduced.

3. Bluetooth setting key API

3.1. BluetoothAdapter

API document address: https://developer.android.google.cn/reference/kotlin/android/bluetooth/BluetoothAdapter

BluetoothAdapterRepresents the local device Bluetooth adapter (operations in this class are thread-safe). Must pass BluetoothAdapterto perform basic Bluetooth tasks such as initiating device discovery, querying the list of bonded (paired) devices, instantiating a Bluetooth device with a known MAC address, creating a BluetoothServerSocket to listen for connection requests from other devices, and starting scanning for Bluetooth LE equipment.

BluetoothAdapterThere are two ways to initialize:

  • JELLY_BEAN_MR1 (API 17) and below, use getDefaultAdapter()
 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  • JELLY_BEAN_MR1 (API 17) or higher, use BluetoothManager.getAdapter()
BluetoothManager btManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (btManager != null) {
    BluetoothAdapter btAdapter = btManager.getAdapter();
}

Fundamentally, BluetoothAdapterthe starting point for all Bluetooth operations.

Once you have it BluetoothAdapter, you can use to getBondedDevices()get a set of BluetoothDeviceobjects representing all paired devices; use to startDiscovery()initiate device discovery; or create BluetoothServerSocketto listen for incoming RFCommconnection requests, and use listenUsingRfcommWithServiceRecord(java.lang.String,java.util.UUID); use to listenUsingL2capChannel()listen for incoming L2CAP connection-oriented channel (CoC) connection requests ; or use startLeScan(android.Bluetooth.BluetoothAdapter.LeScanCallback)Start Bluetooth LE device scan.

3.2. BluetoothDevice

API document address: https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice

BluetoothDeviceIs the entity class of the remote bluetooth device. Via BluetoothDeviceallows you to create a connection with the corresponding device or query information about a Bluetooth device, such as name, address, class, and binding status.

There are several ways to obtain BluetoothDevice:

  • If you already know the mac address of bluetooth, you can use it BluetoothAdapter.getRemoteDevice(String mac)to create a bluetooth device.

  • BluetoothAdapter.getBondedDevices()Get one from the returned set of bound devices. It can then be used via Bluetooth BR/EDR createRfcommSocketToServiceRecord(java.util.UUID)or via Bluetooth LE createL2capChannel(int)to open a communication with a BluetoothSocketremote device.

  • It can also be obtained by BluetoothAdapter.startDiscovery()turning on Bluetooth search and then listening to the broadcast .BluetoothDevice.ACTION_FOUNDBluetoothDevice

3.3. Other key classes

Many practical Bluetooth components are encapsulated in the Android framework directory, but these classes are private classes of the framework and cannot be directly called through the Android API of the application layer. In actual projects, these classes are transplanted into the application and then modified as needed. It is not recommended to directly modify the code of the framework layer! This may cause some native applications to not work properly.

Source location: /frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/

4. Realization of key functions of Bluetooth settings

As a system-level application, system settings need to add the following permissions when using the Bluetooth settings function.

1) Basic Bluetooth permission, which is required to perform any Bluetooth communication, such as requesting connection, accepting connection and transmitting data, etc.

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

2) Bluetooth settings "super administrator" permission, which is required to start device discovery or manipulate Bluetooth settings

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

3) Allows the app to pair bluetooth devices without user interaction and allow or disallow phonebook access or message access

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

4) Location permissions, as Bluetooth scanning can be used to collect user's location information. This information may come from the user's own device, as well as from Bluetooth beacons used in places such as stores and transport facilities

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

4.1. Turn on/off Bluetooth

The main page of the car Bluetooth settings is BluetoothSettingsFragment, it manages the switch of the Bluetooth adapter, it also shows the paired devices and the entry point of the device pairing function.

Source location: /packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothSettingsFragment.java

The method of setting Bluetooth on or off is as follows. BluetoothAdapterThe initialization of Bluetooth and the meaning of each API have been introduced above, so I won’t repeat them here.

private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

private final MenuItem.OnClickListener mBluetoothSwitchListener = item -> {
        item.setEnabled(false);
        if (item.isChecked()) {
            // 开启蓝牙
            mBluetoothAdapter.enable();
        } else {
            // 关闭蓝牙
            mBluetoothAdapter.disable();
        }
    };

In addition, we must listen to the BluetoothAdapter.ACTION_STATE_CHANGED broadcast, which indicates that the Bluetooth state has changed. At this time, we need to synchronize the Bluetooth state to ensure that the internal state machine or UI is always correct.

private  final IntentFilter mIntentFilter  = new  IntentFilter(
        BluetoothAdapter.ACTION_STATE_CHANGED)  ;

@Override
public void onStart() {
    super.onStart();
    // 注册蓝牙状态的广播  
    requireContext().registerReceiver(mReceiver, mIntentFilter) ;
    mLocalBluetoothManager.setForegroundActivity(requireActivity());
    // 页面初始化后,要同步一次蓝牙开关的状态
    handleStateChanged(mBluetoothAdapter.getState());
}

private  final BroadcastReceiver mReceiver  = new BroadcastReceiver() {
    @Override
    public  void onReceive(Context context, Intent intent) {
        int  state = intent.getIntExtra(BluetoothAdapter.E XTRA_STATE,  BluetoothAdapter.ERROR)  ;
        handleStateChanged(state);
    }
};

private  void handleStateChanged(int state) {
    // 暂时清除监听器,以便我们在尝试反映适配器状态时不会更新适配器。  mBluetoothSwitch.setOnClickListener(null ) ;
    switch ( state) {
        case BluetoothAdapter.S TATE_TURNING_ON: 
            mBluetoothSwitch.setEnabled(false ) ;
            mBluetoothSwitch.setChecked(true ) ;
            break ;
        case BluetoothAdapter.S TATE_ON: 
            mBluetoothSwitch.setEnabled(!isUserRestricted());
            mBluetoothSwitch.setChecked(true ) ;
            break ;
        case  BluetoothAdapter.S TATE_TURNING_OFF: 
            mBluetoothSwitch.setEnabled(false ) ;
            mBluetoothSwitch.setChecked(false ) ;
            break ;
        case BluetoothAdapter.S TATE_OFF: 
        default :
            mBluetoothSwitch.setEnabled(!isUserRestricted());
            mBluetoothSwitch.setChecked(false ) ;
    }
    mBluetoothSwitch.setOnClickListener(mBluetoothSwitchListener);
}

Some blogs may see that LocalBluetoothAdapterits source code location is /frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java , but according to the official note, this class is outdated , which is now more recommended BluetoothAdapter.

4.2. Find connected and paired Bluetooth devices

Right after turning on Bluetooth, we need to start searching for Bluetooth devices, but before performing the search, we should first query the set of paired devices to see if the desired device is known. 已连接, 已配对the bluetooth is still displayed on this page,

you need to pay attention to the difference in the description of the text: there is a difference between 已配对the device of the device and the device of the device:已连接

  • 已配对(paired or bonded) means that two devices are aware of each other's existence, have a shared link key that can be used for authentication, and are able to establish an encrypted connection with each other.
  • 已连接(connected) means that the devices currently share the RFCOMM channel and are able to transmit data to each other. The current Bluetooth API requires devices to be paired before an RFCOMM connection can be established. Pairing is performed automatically when an encrypted connection to the Bluetooth API is initiated.

Borrowing the bluetooth setting interface of the mobile phone as an example, 已连接the device in the red box is the device, and the device in the green box is 已配对the device. As shown in the figure below,

there are the following steps to connect to the bluetooth device:

1) Register to broadcast BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED to monitor the connection status of Bluetooth

There are three extras in the intent of the broadcast, which are

BluetoothAdapter.EXTRA_CONNECTION_STATE: current connection state

BluetoothAdapter . EXTRA_PREVIOUS_CONNECTION_STATE: previous connection state

BluetoothDevice.EXTRA_DEVICE: Bluetooth device

Registering for this broadcast requires the bluetooth permission android.Manifest.permission.BLUETOOTH.

  1. Determine the connection status, if it is connected, get the connected Bluetooth device through EXTRA_DEVICE

Obtaining paired Bluetooth devices has the following steps:

1) Register and broadcast BluetoothDevice . ACTION_BOND_STATE_CHANGED monitors Bluetooth pairing status

There are four extras in the intent of the broadcast, which are

  • BluetoothDevice.EXTRA_BOND_STATE: current pairing state
  • BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE: previous pairing state
  • BluetoothDevice.EXTRA_DEVICE: Bluetooth device
  • BluetoothDevice.EXTRA_REASON: When EXTRA_BOND_STATE is BOND_NONE, a result code can be obtained through EXTRA_REASON.

2) Determine the pairing status, if it is paired, get the connected Bluetooth device through EXTRA_DEVICE


After understanding the steps, let's see how it is handled in the source code of the car Settings.

In BluetoothSettingsFragmentthe layout file bluetooth_settings_fragment.xml , a BluetoothBondedDevicesPreferenceController class is used . The upper layer of this class inherits from BluetoothPreferenceController , and BluetoothPreferenceControllerregisters LocalBluetoothManager.BluetoothEventManagerone BluetoothCallbackto monitor the status callback of the Bluetooth device.

private final LocalBluetoothManager mBluetoothManager;
   
protected void onStartInternal() {
    mBluetoothManager.getEventManager().registerCallback(this);
}

LocalBluetoothManager.BluetoothEventManagerIt is a private class of the framework. All Bluetooth broadcast events are registered and distributed here. It is the class we need to focus on.

// 蓝牙开关的广播 
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());

// 蓝牙连接状态的广播 
addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,new ConnectionStateChangedHandler());

// 蓝牙扫描的广播 
addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED,new ScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED,new ScanningStateChangedHandler(false));
addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());

// 蓝牙配对状态的广播 
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());

// Fine-grained state broadcasts a
ddHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());

// 活跃设备的广播 
addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, new ActiveDeviceChangedHandler());
addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, new ActiveDeviceChangedHandler());
addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,new ActiveDeviceChangedHandler());

// 耳机状态改变广播 
addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,new AudioModeChangedHandler());
addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,new AudioModeChangedHandler());

// ACL 连接更改的广播 
addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());

Source location: /frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java

Below is the bluetooth device that handles the connection state

 // Generic connected/not broadcast
addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED, new ConnectionStateChangedHandler());

// 这个 Handler 不是Android.OS中的handler,它只是一个接口
private  class ConnectionStateChangedHandler implements Handler {
    @Override
    public  void onReceive(Context context, Intent intent, BluetoothDevice device) {
        // 更新本地缓存,并返回一个二次封装类
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.ERROR);
        // 分发 连接 状态
dispatchConnectionStateChanged(cachedDevice, state);
    }
}

Below is the bluetooth device that handles the pairing state

 // Pairing broadcasts
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());

public  void onReceive (Context context, Intent intent, BluetoothDevice device){
    if (device == null) {
        Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE" );
        return;
    }
    int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
    // 更新本地缓存,并返回一个二次封装的蓝牙实体类
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    if (cachedDevice == null) {
        Log.w(TAG, "Got bonding state changed for " + device + ", but we have no record of that device." );
        cachedDevice = mDeviceManager.addDevice(device);
    }
    // 分发 配对 状态
 for (BluetoothCallback callback : mCallbacks) {
        callback.onDeviceBondStateChanged(cachedDevice, bondState);
    }
    cachedDevice.onBondingStateChanged(bondState);

    if (bondState == BluetoothDevice.BOND_NONE) {
        /* 检查我们是否需要移除其他hearing aid设备 */
 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
            mDeviceManager.onDeviceUnpaired(cachedDevice);
        }
        int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
                BluetoothDevice.ERROR);
        // 显示错误信息
        showUnbondMessage(context, cachedDevice.getName(), reason);
    }
}

4.3. Scan for Bluetooth devices

After turning on Bluetooth, we need to start scanning for Bluetooth devices. There are several steps to retrieve external Bluetooth devices:

1) Register BluetoothAdapter.ACTION_DISCOVERY_STARTED, BluetoothAdapter.ACTION_DISCOVERY_FINISHED to monitor Bluetooth scanning status

2) Register BluetoothDevice.ACTION_FOUND to monitor whether a Bluetooth device is found during the scan

The broadcast intent contains the following extras

  • BluetoothDevice.EXTRA_DEVICE: Bluetooth device
  • BluetoothDevice.EXTRA_CLASS: BluetoothClass, which represents the Bluetooth class, which describes the general characteristics and functions of the device. For example, a Bluetooth class would specify a generic device type, such as a phone, computer, or headset, and whether it can provide services such as audio or telephony. Each Bluetooth class consists of zero or more service classes and a device class. Device classes are further divided into primary and secondary device class components.

The following extras are not always available, and they are not commonly used, so pay attention

  • BluetoothDevice.EXTRA_NAME: The name of the Bluetooth device
  • BluetoothDevice.EXTRA_RSSI: The signal strength of the Bluetooth device
  • BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER: It contains information whether the device was discovered as a member of a coordinated set. Pairing with a device that is part of the set will trigger pairing with the rest of the set members. See the Bluetooth CSIP Specification for details.

3) Call to BluetoothAdapter.startDiscovery()enable Bluetooth scanning

4) Obtain the scanned Bluetooth device from the intent

The above steps require the android.permission.BLUETOOTH permission, and the Android system above API 31 needs the android.permission.BLUETOOTH_SCAN permission.


Ok, let's continue to see how the source code of the car Settings handles scanning. Display the list of Bluetooth devices
in the car Settings . BluetoothPairingSelectionFragmentWhile the fragment is visible, there will be a progress bar to indicate discovery or pairing progress.

UI source location: /packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragment.java

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:title="@string/bluetooth_pair_new_device"
    android:key="@string/psk_bluetooth_pairing_selection">
    <!-- 本机蓝牙的名称 -->
    <Preference
        android:key="@string/pk_bluetooth_name"
        android:title="@string/bluetooth_name"
        settings:controller="com.android.car.settings.bluetooth.BluetoothNamePreferenceController"/>
    <!-- 未配对的蓝牙设备 -->
    <PreferenceCategory
        android:key="@string/pk_bluetooth_available_devices"
        android:title="@string/bluetooth_available_devices"
        settings:controller="com.android.car.settings.bluetooth.BluetoothUnbondedDevicesPreferenceController"/>
    <!-- 本机蓝牙设备的地址 -->
    <Preference
        android:icon="@drawable/ic_settings_about"
        android:key="@string/pk_bluetooth_address"
        android:selectable="false"
        settings:controller="com.android.car.settings.bluetooth.BluetoothAddressPreferenceController"/>
</PreferenceScreen>

Page logic source location: /packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceController.java

The source code to start or stop Bluetooth search is as follows

private void enableScanning() {
    mIsScanningEnabled = true;
    if (!mBluetoothAdapter.isDiscovering()) {
        // 开启扫描
        mBluetoothAdapter.startDiscovery();
    }
    // 开启蓝牙可见
    mAlwaysDiscoverable.start();
    getPreference().setEnabled(true);
}

private  void disableScanning() {
    mIsScanningEnabled = false;
    getPreference().setEnabled(false);
    // 关闭蓝牙可见
    mAlwaysDiscoverable.stop();
    if (mBluetoothAdapter.isDiscovering()) {
        // 取消扫描
        mBluetoothAdapter.cancelDiscovery();
    }
}

After the interface actively turns on the Bluetooth search, the monitoring of the three broadcasts of ACTION_DISCOVERY_STARTED , * ACTION_DISCOVERY_FINISHED , and ACTION_FOUND are all completed in the private code of the framework layer. As mentioned before, the broadcasting time of Bluetooth is basically completed in this class for monitoring and event distribution.

Source location: /frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java

addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));

private  class ScanningStateChangedHandler implements Handler {
    private  final  boolean mStarted;

    ScanningStateChangedHandler(boolean started) {
        mStarted = started;
    }

    public  void onReceive(Context context, Intent intent, BluetoothDevice device) {
        for (BluetoothCallback callback : mCallbacks) {
            callback.onScanningStateChanged(mStarted);
        }
        mDeviceManager.onScanningStateChanged(mStarted);
    }
}   

Finally, when the callback is received in the UI interface, the search is started if conditions permit.

@Override
public  void onScanningStateChanged(boolean started) {
    LOG.d( "onScanningStateChanged started: " + started + " mIsScanningEnabled: " + mIsScanningEnabled);
    if (!started && mIsScanningEnabled) {
        enableScanning();
    }
}

After the search is turned on, it is necessary to process the searched Bluetooth devices.

addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());

private  class BluetoothBroadcastReceiver extends BroadcastReceiver {

    @Override
    public  void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        Handler handler = mHandlerMap.get(action);
        if (handler != null) {
            handler.onReceive(context, intent, device);
        }
    }
}

After getting it BluetoothDevice, it needs to be filtered to keep only unpaired and unconnected entities, and finally encapsulated BluetoothDeviceinto CachedBluetoothDevicea callback class to display the UI, and the searched Bluetooth devices will be displayed on the UI.

CachedBluetoothDeviceIt is BluetoothDevicea further encapsulation of the pair, and its internal functions such as Bluetooth connection, pairing, and status acquisition are realized. It is a private class of the framework layer, source location: /frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java

// BluetoothEventManager.java
private  class DeviceFoundHandler implements Handler {

    public  void onReceive(Context context, Intent intent, BluetoothDevice device) {
        short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
        String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
        // TODO 获取UUID。它们应适用于2.1版本。 
 // 现在跳过,有一个bluez问题,即使是2.1版本,也无法获得uuid。
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
        if (cachedDevice == null) {
            cachedDevice = mDeviceManager.addDevice(device);
            Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " + cachedDevice);
        } else  if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
&& !cachedDevice.getDevice().isConnected()) {
            // 调度设备添加回调以在发现模式下显示绑定但未连接的设备
dispatchDeviceAdded(cachedDevice);
            Log.d(TAG, "DeviceFoundHandler found bonded and not connected device:" + cachedDevice);
        } else {
            Log.d(TAG, "DeviceFoundHandler found existing CachedBluetoothDevice:" + cachedDevice);
        }
        cachedDevice.setRssi(rssi);
        cachedDevice.setJustDiscovered(true);
    }
}

void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice){
    for (BluetoothCallback callback : mCallbacks) {
        callback.onDeviceAdded(cachedDevice);
    }
}
// BluetoothDevicesGroupPreferenceController.java
@Override
public  final  void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
    // 刷新 UI
    refreshUi();
}

In the last step refreshUi, it can be seen that the cachedDevice is not used to update the UI, because LocalBluetoothManagerall the scanned Bluetooth devices have been cached, and only need to take out the list LocalBluetoothManagerfrom it to update the UI interface.

4.4 Bluetooth pairing

Bluetooth pairing has the following steps:

1) Register android.bluetooth.device.action.PAIRING_REQUEST broadcast

2) Cancel the scanning process

It is important to stop the Bluetooth search before performing pairing, as the search process can significantly reduce the bandwidth available for the connection, causing the connection operation to fail.

3) Execute BluetoothDevice.createBond()pairing

After performing pairing, enable the following permissions of the Bluetooth device as required

BluetoothDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED)
BluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED)

4) Process the PAIRING_REQUEST broadcast message and display the corresponding UI


Continue to see how it is handled in the source code, click the unpaired Bluetooth device in the Bluetooth device list

Source location: /packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceController.java

@Override
protected  void onDeviceClickedInternal(CachedBluetoothDevice cachedDevice) {
    if (cachedDevice.startPairing()) {
        LOG.d( "startPairing" );
        // 如果有服务端允许(通常是电话),则表明该客户端(车辆)希望访问联系人(PBAP)和消息(MAP)。
cachedDevice.getDevice().setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
        cachedDevice.getDevice().setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
    } else {
        BluetoothUtils.showError(getContext(), cachedDevice.getName(),
                R.string.bluetooth_pairing_error_message);
        refreshUi();
    }
}

Source location: /frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java

public boolean startPairing() {
    // 扫描时配对是不可靠的,因此取消扫描
if (mLocalAdapter.isDiscovering()) {
        mLocalAdapter.cancelDiscovery();
    }
    if (!mDevice.createBond()) {
        return false;
    }
    return true;
}

There will be a dialog prompt to the user during the Bluetooth pairing process, and this dialog also needs to be realized by listening to the broadcast.

<receiver android:name=".bluetooth.BluetoothPairingRequest">
    <intent-filter>
        <action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
    </intent-filter>
</receiver>

BluetoothPairingRequestIs the receiver for any Bluetooth pairing requests. It checks to see if the Bluetooth settings are currently visible and displays a PIN, password or confirmation input dialog. Otherwise, it fires up BluetoothPairingService, which fires up a notification in the status bar, which when clicked shows the same dialog.

public final class BluetoothPairingRequest extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
            return;
        }
        // 将广播意图转换为活动意图
Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context, intent);

        PowerManager powerManager =
                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        BluetoothDevice device =
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        String deviceAddress = device != null ? device.getAddress() : null;
        String deviceName = device != null ? device.getName() : null;
        // 判断dialog 是否已经显示
boolean shouldShowDialog = BluetoothUtils.shouldShowDialogInForeground(
                context, deviceAddress, deviceName);
        // 判断屏幕是否开启
if (powerManager.isInteractive() && shouldShowDialog) {
            // 由于屏幕已打开且BT相关的活动在前台,因此只需打开对话框
context.startActivityAsUser(pairingIntent, UserHandle.CURRENT);
        } else {
            // 发布一个通知,用于触发 dialog
intent.setClass(context, BluetoothPairingService.class);
            context.startServiceAsUser(intent, UserHandle.CURRENT);
        }
    }
}
 

BluetoothPairingServiceThe core code is as follows, in BluetoothPairingServicewhich you also need to listen to the ACTION_BOND_STATE_CHANGED broadcast, and if the pairing is completed, you need to cancel the message in the status bar.

 // 转换 intent 的方法。
public static Intent getPairingDialogIntent(Context context, Intent intent) {
    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    // 获取配对类型
int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
            BluetoothDevice.ERROR);
    Intent pairingIntent = new Intent();
    pairingIntent.setClass(context, BluetoothPairingDialog.class);
    pairingIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, type);
    // 获取配对的key
if (type == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION ||
            type == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY ||
            type == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
        int pairingKey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY,
                BluetoothDevice.ERROR);
        pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pairingKey);
    }
    pairingIntent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
    pairingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    return pairingIntent;
}

When Bluetooth is paired, it will display the PIN, and whether you agree to read the phonebook and other information. These contents are included in the intent of the ACTION_PAIRING_REQUEST broadcast. The specific acquisition method has been added to the above code. It should be noted that different types need to display different interfaces during pairing.

Prompt for key/PIN required:

BluetoothDevice.PAIRING_VARIANT_PIN
BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS
BluetoothDevice.PAIRING_VARIANT_PASSKEY

Prompt the user to agree to the pairing request:

BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
BluetoothDevice.PAIRING_VARIANT_CONSENT:
BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:

Notify the user of the pairing request and show them the device's PIN/passkey:

BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN

The next step is for the user to confirm whether to agree to the pairing request:

  • The processing flow of the user rejecting the pairing request:
private BluetoothDevice mDevice;

@Override
public void onDialogNegativeClick(BluetoothPairingDialogFragment dialog) {
    onCancel();
}

/**
* 一种正确结束与蓝牙设备通信的方法。
* BluetoothPairingDialogFragment 关闭时将调用它。
*/
public void onCancel() {
    LOG.d("Pairing dialog canceled");
    mDevice.cancelPairing();
}
  • The user agrees to the processing flow of the pairing request:
@Override
public void onDialogPositiveClick(BluetoothPairingDialogFragment dialog) {
    if (getDialogType() == USER_ENTRY_DIALOG) {
        onPair(mUserInput);
    } else {
        onPair(null);
    }
}

/**
* 处理与蓝牙设备的必要通信以建立成功配对
* 参数:密码 - - 我们将尝试与设备配对的密码。
*/
private void onPair(String passkey) {
    LOG.d("Pairing dialog accepted");
    switch (mType) {
        case BluetoothDevice.PAIRING_VARIANT_PIN:
        case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
            mDevice.setPin(passkey);
            break;
        case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
            int pass = Integer.parseInt(passkey);
            break;
        
        case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
        case BluetoothDevice.PAIRING_VARIANT_CONSENT:
            mDevice.setPairingConfirmation(true);
            break;

        case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
        case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
        case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
            // Do nothing.
break;
        default:
            LOG.e("Incorrect pairing type received");
    }
}

The above is the whole process of a Bluetooth pairing. If it is a paired Bluetooth device, just connect it directly

public  void connect() {
    if (!ensurePaired()) {
        return;
    }
    mConnectAttempted = SystemClock.elapsedRealtime();
    connectAllEnabledProfiles();
}

private  void connectAllEnabledProfiles() {
    synchronized (mProfileLock) {
        // 如果没有,请尝试初始化配置文件。
 if (mProfiles.isEmpty()) {
            // 如果 mProfiles 为空,则不要调用 updateProfiles。
 // 这会在配对期间导致与 carkits 的竞争条件,其中 RemoteDevice.UUIDs 已从蓝牙堆栈更新,但 ACTION.uuid 尚未发送。
 // 最终将收到 ACTION.uuid,这将触发各种配置文件的连接如果 UUID 尚不可用,则连接将在 ACTION_UUID 意图到达时发生。
Log.d(TAG, "No profiles. Maybe we will connect later for device " + mDevice);
            return;
        }
        mLocalAdapter.connectAllEnabledProfiles(mDevice);
    }
}

private  boolean ensurePaired() {
    if (getBondState() == BluetoothDevice.BOND_NONE) {
        startPairing();
        return  false;
    } else {
        return  true;
    }
}
  1. Set bluetooth visibility

By default, other bluetooth devices cannot search for the current bluetooth device. You must use the following code to set the bluetooth device to the visible state. timeout is the time when the bluetooth is visible. The length can be set to 1 hour.

BluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);

AlwaysDiscoverableClass that manages Bluetooth visibility in native system settings .

Source location: /packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceController.java

This class registers BluetoothAdapter.ACTION_SCAN_MODE_CHANGED , and when SCAN_MODE changes, set Bluetooth to be visible again, so that BluetoothAdapter can be kept in discoverable mode indefinitely. By default, setting the scan mode to BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE will time out, but for pairing we want to keep the device discoverable at all times while the page is scanning.

private static final class AlwaysDiscoverable extends BroadcastReceiver {

    private final Context mContext;
    private final BluetoothAdapter mAdapter;
    private final IntentFilter mIntentFilter = new IntentFilter(
            BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);

    private boolean mStarted;

    AlwaysDiscoverable(Context context, BluetoothAdapter adapter) {
        mContext = context;
        mAdapter = adapter;
    }

    /**
     * 将适配器扫描模式设置为 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE。 
     * 当不再需要发现模式时,start() 调用应该有对 stop() 的匹配调用。
     */
    void start() {
        if (mStarted) {
            return;
        }
        mContext.registerReceiver(this, mIntentFilter);
        mStarted = true;
        setDiscoverable();
    }

    void stop() {
        if (!mStarted) {
            return;
        }
        mContext.unregisterReceiver(this);
        mStarted = false;
        mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        setDiscoverable();
    }

    private void setDiscoverable() {
        if (mAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
        }
    }
}

5. Summary

The above is the analysis of the key parts of Bluetooth settings in the native system settings. In fact, after reading this blog, it will not make you proficient in the development of Bluetooth settings immediately, because there are still many details in the settings function that are not exhaustive, such as: monitoring active devices, etc. Therefore, when developing system applications, it is the best way for us to read native code.

The purpose of this blog, as mentioned in the preface, is to give developers a general understanding of the in-vehicle system application itself. The first application I personally switched from the mobile Internet to a car was to write system settings. Since I didn’t know anything about system settings at the time, I had been using the Android application layer API for development, and I didn’t think of transplanting framework code. The result was a ton BUG, I believe that after reading this article, you may be able to avoid some detours.

Guess you like

Origin blog.csdn.net/linkwj/article/details/127221729