Android系统中的bluetooth模块
1. 蓝牙是什么
蓝牙是一种低功耗的无线连接技术,是一种设备间短距离的无线通讯方式,这句话表明蓝牙以下几个特性:
- 蓝牙是一种无线通讯方式,即表示该通讯需要有对应的协议支持(蓝牙无线通信协议标准) 。
- 蓝牙跨设备使用。
- 低耗能技术。
- 蓝牙属于短距离通讯方式。
2. 蓝牙有什么
蓝牙技术有两种类型:
- Basic Rate/Enhanced Data Rate (BR/EDR)基本速率/增强数据速率即所谓的传统蓝牙技术(蓝牙版本2.0/2.1):仅支持P2P一种通信方式,即1:1设备间通信,具有持续无线连接、优化音频流的特点,所以是蓝牙耳机、蓝牙扬声器等音频传输的理想方案
- Low Energy (LE)低功耗即所谓的新型的低功耗蓝牙技术(蓝牙版本4.0/4.1/4.2/4.3)
所以蓝牙模块可以分为经典蓝牙模块(v1.1/1.2/2.0/2.1/3.0),低功耗蓝牙模块(v4.0/4.1/4.2),以及蓝牙双模模块(支持蓝牙所有版本,兼容低功耗蓝牙及经典蓝牙)。
蓝牙技术4种通信方式:
- P2P通信方式(旧版本):1:1设备间通信,具有持续无线连接、优化音频流的特点,所以是蓝牙耳机、蓝牙扬声器等音频传输的理想方案
- P2P(point-to-point)(点对点):1:1支持短时间无限连接,优化了数据传输能量消耗,可用于无线键盘、无线鼠标等
- broadcast(广播信息):1:m。可以实现本地化信息共享。广播信息顾名思义,一设备广播信息,其他对该信息感兴趣的设备接受该信息并进行处理。比如beacon
- mesh(网格):m:m
蓝牙常用协议:
. | 含义 | 作用 | 举例 |
---|---|---|---|
OppProfile | Object Push Profie | 文件传输协议:用于蓝牙设备间的文件传输 | 手机间的文件传输 |
PbapServerProfile | Phone Book Access Profile(PSE) | 读取联系人协议:作为server,本设备的联系人可共享给其他设备 | 提供联系人列表 |
PbapClientProfile | Phone Book Access Profile(PCE) | 读取联系人协议:作为client角色,本设备可读取server端的联系人 | 读取联系人列表 |
A2dpProfile | Advanced Audio Distribution Profile(SRC:Source) | 高级音频分发协议:作为server提供音频源 | 例如可以提供音频源的手机 |
A2dpSinkProfile | Advanced Audio Distribution Profile(SINK) | 高级音频分发协议:作为client播放接收到的音频 | 车载蓝牙,蓝牙音响 |
HeadsetProfile | Headset Profile | 耳机协议:提供手机音频 | 连接蓝牙耳机 |
HfpClientProfile | Hands-Free Profile | 免提设备:播放音频 | 蓝牙耳机 |
HidProfile | Human Interface Device | 人机接口设备 | 蓝牙鼠标,蓝牙键盘 |
MapProfile | Message Access Profile | 读取短消息协议 | |
SapProfile | SIM Access Profile | 读取sim卡协议 |
3. 蓝牙需要改什么
蓝牙与Android关系:
- Google推出的各Android系统:所支持的蓝牙协议profile均是开启状态
- 芯片提供商(常见的诸如高通、mtk)修改后的Android源码–开发中称之为base代码:新增或者修改某些蓝牙profile
- 开发商拿到base代码进行进一步加工:新增或者修改某些profile
蓝牙堆栈的常规结构:
* 应用框架:
处于应用框架级别的是应用代码,它利用 android.bluetooth API 与蓝牙硬件进行交互。此代码在内部通过 Binder IPC 机制调用蓝牙进程。
- 蓝牙系统服务
蓝牙系统服务(位于 packages/apps/Bluetooth 中)被打包为 Android 应用,并在 Android 框架层实现蓝牙服务和配置文件。该应用通过 JNI 调用 HAL 层。 - JNI
与 android.bluetooth 相关联的 JNI 代码位于 packages/apps/Bluetooth/jni 中。当发生特定蓝牙操作时(例如发现设备时),JNI 代码会调用 HAL 层并从 HAL 接收回调。 - HAL
硬件抽象层定义了 android.bluetooth API 和蓝牙进程会调用的标准接口,并且您必须实现该接口才能使蓝牙硬件正常工作。蓝牙 HAL 的头文件是 hardware/libhardware/include/hardware/bluetooth.h。另外,请查看所有 hardware/libhardware/include/hardware/bt_*.h 文件。 - 蓝牙堆栈
系统为您提供了默认蓝牙堆栈(位于 system/bt 中)。该堆栈会实现常规蓝牙 HAL,并通过扩展程序和更改配置对其进行自定义。 - 供应商扩展程序
要添加自定义扩展程序和用于跟踪的 HCI 层,一般可以创建一个 libbt-vendor 模块并指定这些组件。
蓝牙核心架构:
蓝牙代码的实现主要包括3个方面:
- 界面UI
- 设置应用中蓝牙的ui
- 蓝牙本身这个系统应用中的ui
- 蓝牙开关默认值
- 协议配置开关:手机是否要支持各种协议
蓝牙代码分布:
系统应用设置Settings中的蓝牙相关,包括蓝牙开关,蓝牙扫描,蓝牙配对框,蓝牙重命名框,蓝牙选择框等等。
系统中有个蓝牙应用Bluetooth,包含蓝牙文件传入传出历史记录,蓝牙配对框,蓝牙文件传输框等等。
蓝牙协议的具体实现
集成的一些蓝牙接口
4. 对蓝牙功能添加/修改
开发一个测试工具,主要对bluetooth的Channel进行测试
- 通过暗码进行打开这个测试工具
- 可以测试不通的Channel
关于这个工具的开发其实系统已经提供了相应的接口,接口的实现都是不用管的,只要将接口层层包装,并在工具apk中调用包装好的接口即可。
4.1 首先,看看系统什么地方定义了该接口:
在文件hardware/libhardware/include/hardware/bluetooth.h
中有个如下结构体:
/** Represents the standard Bluetooth DM interface. */
typedef struct {
/** set to sizeof(bt_interface_t) */
size_t size;
/**
* Opens the interface and provides the callback routines
* to the implemenation of this interface.
*/
int (*init)(bt_callbacks_t* callbacks );
/** Enable Bluetooth. */
int (*enable)(bool guest_mode);
/** Disable Bluetooth. */
int (*disable)(void);
/*此处省略*/
/** Get Bluetooth profile interface */
const void* (*get_profile_interface) (const char *profile_id);
/** Bluetooth Test Mode APIs - Bluetooth must be enabled for these APIs */
/* Configure DUT Mode - Use this mode to enter/exit DUT mode */
int (*dut_mode_configure)(uint8_t enable);
/* Send any test HCI (vendor-specific) command to the controller. Must be in DUT Mode */
int (*dut_mode_send)(uint16_t opcode, uint8_t *buf, uint8_t len);
/** BLE Test Mode APIs */
/* opcode MUST be one of: LE_Receiver_Test, LE_Transmitter_Test, LE_Test_End */
int (*le_test_mode)(uint16_t opcode, uint8_t *buf, uint8_t len);
/* 该结构体中的函数指针dut_mode_configure && le_test_mode就是提供给上层使用的负责蓝牙的开关及基本控制标准接口,本次开发主要就使用这2个接口。*/
} bt_interface_t;
4.2 其次,问题就转换为如何调用这个2接口了。 在 apk中调用c/c++层的接口需要通过JNI层将这2接口个进行包装
- 在文件
packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
文件中添加对应方法
#define HCI_LE_TRANSMITTER_TEST_OPCODE 0x201E
#define HCI_LE_END_TEST_OPCODE 0x201F
static jint setTestModeNative(JNIEnv *env, jobject object, jint mode) {
ALOGD("%s setTestModeNative mode = %d",__FUNCTION__, mode);
#if defined (HAVE_BLUETOOTH) && defined (ENG_MODE)
ALOGI("%s mode = %d",__FUNCTION__, mode);
if (!sBluetoothInterface) return -1;
return sBluetoothInterface->dut_mode_configure(mode);/*调用蓝牙hal层的接口*/
#endif
return -1;
}
static jint setBtChannelNative(JNIEnv *env, jobject object, jint position) {
ALOGD("%s setBtChannelNative position = %d",__FUNCTION__, position);
#if defined (HAVE_BLUETOOTH) && defined (ENG_MODE)
ALOGI("%s position = %d",__FUNCTION__, position);
if (!sBluetoothInterface) return -1;
unsigned char buf[3];
memset(buf, 0, sizeof(buf));
if (position < 0) {
return sBluetoothInterface->le_test_mode(HCI_LE_END_TEST_OPCODE, buf, 0);
}
buf[0] = position; /* tx_channel 0-39*/
buf[1] = 0x25; /* length of test data 0-37*/
buf[2] = 0; /* packet payload <9*/
return sBluetoothInterface->le_test_mode(HCI_LE_TRANSMITTER_TEST_OPCODE, buf, 3);
#endif
return -1;
}
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"setSocketOptNative", "(III[BI)I", (void*) setSocketOptNative}
/*在数组sMethods中注册新加的2个方法为native方法供java层使用*/
,{"setTestModeNative", "(I)I", (void *)setTestModeNative},
{"setBtChannelNative","(I)I",(void *)setBtChannelNative}
/**/
};
将新增的2个方法包装到AdapterService.java服务中,运行不同进程的应用调用。
a. 在文件frameworks/base/core/java/android/bluetooth/IBluetooth.aidl中添加新方法的调用接口,具体如下:
interface IBluetooth
{
/*此处省略*/
int setBtTestMode(int mode);
int setBtChannel(int position);
/*此处省略*/
}
在packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java中实现IBluetooth中的2个接口,这样对于任何持有AdapterService句柄的对象都可以使用新加的2个方法。
import android.bluetooth.IBluetooth;/*导入aidl接口,实现方法跨进程调用*/
public class AdapterService extends Service {
/*此处省略*/
static {
System.loadLibrary("bluetooth_jni");/*load jni静态库*/
classInitNative();/*调用native中的init方法,为结构体bt_interface_t赋值*/
}
private native int setTestModeNative(int mode);
private native int setBtChannelNative(int position);
/**
* Handlers for incoming service callsH
*/
private AdapterServiceBinder mBinder;
/**
* The Binder implementation must be declared to be a static class, with
* the AdapterService instance passed in the constructor. Furthermore,
* when the AdapterService shuts down, the reference to the AdapterService
* must be explicitly removed.
*
* Otherwise, a memory leak can occur from repeated starting/stopping the
* service...Please refer to android.os.Binder for further details on
* why an inner instance class should be avoided.
*
*/
private static class AdapterServiceBinder extends IBluetooth.Stub {
/*此处省略*/
public int setBtTestMode(int mode) {
Log.d(TAG, "setBtTestMode mode " + mode);
AdapterService service = getService();
if (service == null) return -1;
return service.setTestModeNative(mode);
}
public int setBtChannel(int position) {
Log.d(TAG, "setBtChannel position " + position);
AdapterService service = getService();
if (service == null) return -1;
return service.setBtChannelNative(position);
}
}
/*此处省略*/
}
- 到目前为止,添加的2个方法还处在packages/apps/Bluetooth模块中,继续将新加的方法包装到BluetoothAdapter.java中,方便应用程序调用,具体修改如下。
public final class BluetoothAdapter {
/*此处省略*/
public int setBtTestMode(int mode) {
Log.d(TAG, "setBtTestMode mode : " + mode);
//IBluetooth service = mBluetoothAdapter.mService;
if (mService == null) {
return -1;
}
try {
return mService.setBtTestMode(mode);
} catch (Exception ex) {
Log.w(TAG, "Unhandled exception: " + ex);
}
return -1;
}
public int setBtChannel(int position) {
Log.d(TAG, "setBtChannel position : " + position);
//IBluetooth service = mBluetoothAdapter.mService;
if (mService == null) {
return -1;
}
try {
return mService.setBtChannel(position);
} catch (Exception ex) {
Log.w(TAG, "Unhandled exception: " + ex);
}
return -1;
}
/*此处省略*/
}
4.3 最后,写个测试apk,调用封装好的方法。
package com.android.BluetoothTestMode;
import com.android.BluetoothTestMode.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Toast;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Spinner;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Handler;
import android.os.Message;
public class BluetoothTestMode extends Activity implements AdapterView.OnItemSelectedListener {
private Context mContext;
private BluetoothAdapter mBtAdapter;
private final String TAG = "BluetoothTestMode";
private static final int ENABLE_BT_TEST_MODE_DELAY = 3;
private Button mButton01 = null;
private Button mButton02 = null;
private Spinner mSpinner = null;
private int mBluetoothChannel = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
// Get the local Bluetooth adapter
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
setContentView(R.layout.main);
mButton01 = (Button) findViewById(R.id.Button01);
mButton02 = (Button) findViewById(R.id.Button02);
//bluetooth test mode channel setting
mSpinner = (Spinner) findViewById(R.id.Bluetooth_Channel_Spinner);
mSpinner.setSelection(0, true);
mSpinner.setOnItemSelectedListener(this);
mButton01.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mButton01.setEnabled(false);
mButton02.setEnabled(true);
if (!mBtAdapter.isEnabled()) {
mBtAdapter.enable();
synchronized(mHandler) {
if(mHandler.hasMessages(ENABLE_BT_TEST_MODE_DELAY)){
mHandler.removeMessages(ENABLE_BT_TEST_MODE_DELAY);
}
mHandler.sendEmptyMessageDelayed(ENABLE_BT_TEST_MODE_DELAY, 5000);
}
Toast.makeText(BluetoothTestMode.this, "BT is turning on, please wait...", Toast.LENGTH_SHORT).show();
}else {
int ret = mBtAdapter.setBtTestMode(1);
if (ret == -1) {
mButton01.setEnabled(true);
mButton02.setEnabled(false);
Toast.makeText(BluetoothTestMode.this, "Test mode enable failed", Toast.LENGTH_SHORT).show();
}
Log.d(TAG, "BT already ON! enableBtTestMode " + ret);
}
}
});
mButton02.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mButton01.setEnabled(true);
mButton02.setEnabled(false);
if (mBtAdapter.isEnabled()) {
mBtAdapter.setBtChannel(-1);
mBtAdapter.setBtTestMode(0);
mBtAdapter.disable();
Toast.makeText(BluetoothTestMode.this, "BT disabled...", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(BluetoothTestMode.this, "BT already OFF!", Toast.LENGTH_SHORT).show();
}
}
});
}
/**
* Called when the activity will start interacting with the user.
*/
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
}
/**
* Called when the system is about to start resuming a previous activity.
*/
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
}
/**
* The final call you receive before your activity is destroyed.
*/
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (mBtAdapter.isEnabled()) {
mBtAdapter.setBtChannel(-1);
mBtAdapter.setBtTestMode(0);
mBtAdapter.disable();
}
}
//bluetooth test mode channel setting
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent == mSpinner) {
mBluetoothChannel = position;
if (!mBtAdapter.isEnabled()) {
mBtAdapter.enable();
}
int ret = mBtAdapter.setBtChannel(-1);
Log.d(TAG,"setBtChannel end test " + ret);
ret = mBtAdapter.setBtChannel(mBluetoothChannel);
Log.d(TAG,"setBtChannel start test " + ret);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
//
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == ENABLE_BT_TEST_MODE_DELAY){
int ret = mBtAdapter.setBtTestMode(1);
if (ret == -1) {
mButton01.setEnabled(true);
mButton02.setEnabled(false);
Toast.makeText(BluetoothTestMode.this, "Test mode enable failed", Toast.LENGTH_SHORT).show();
}
Log.d(TAG, "enableBtTestMode " + ret);
}
}
};
}
参考:带你解锁蓝牙skill