1,概述
1.1 HID协议
HID协议: Hunman Interface Device Profile人机交互设备协议
使用场景:支持人机交互设备之间的控制
市场产品:蓝牙键盘,蓝牙鼠标,蓝牙游戏手柄等。
1.2 代码路径
客户端: frameworks\base\core\java\android\bluetooth
服务端: packages\apps\Bluetooth\src\com\android\bluetooth\ hid
HidDevService.java hid协议的服务端
开发流程和健康设备类似,但是稍微麻烦
2,接口
接口如下
3,开发步骤
在官方文档中有一个建立通信的流程:
1、调用getProfileProxy(Context,BluetoothProfile.ServiceListener, int)来获取代理对象的连接。
2、创建BluetoothHidDeviceAppSdpSettings, BluetoothHidDeviceAppQosSettings对象,创建BluetoothHidDeviceCallback回调,调用registerApp方法注册
3、将手机与设备配对,并且进行连接。
4、实现BluetoothHidDeviceCallback的7个回调方法
5、调用sendReport方法分别实现蓝牙鼠标,蓝牙键盘等。
3.1 获取客户端代理对象
一般在oncreate方法中,直接调用getProfileProxy方法,这个没什么好说的。
BluetoothAdapter.getDefaultAdapter().getProfileProxy(getApplicationContext(),
mProfileServiceListener,BluetoothProfile. HID_DEVICE);
private BluetoothProfile.ServiceListener mProfileServiceListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEALTH){
mBluetoothHealth = null;
}
}
@SuppressLint("NewApi")
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEALTH) {
mHidDevice = (BluetoothHidDevice) proxy;
// 获取代理对象之后就进行注册
...
}
}
};一般经过这个步骤,客户端的BluetoothHidDevice对象已经和服务端的HidDevService对象绑定了。
3.2 注册registerApp
BluetoothHidDeviceAppSdpSettings sdp = new BluetoothHidDeviceAppSdpSettings(
HidConsts.NAME, HidConsts.DESCRIPTION, HidConsts.PROVIDER,
BluetoothHidDevice.SUBCLASS1_COMBO, HidConsts.DESCRIPTOR);
BluetoothHidDeviceAppQosSettings inQos = new BluetoothHidDeviceAppQosSettings(
BluetoothHidDeviceAppQosSettings.SERVICE_GUARANTEED, 200, 2, 200,
10000 /* 10 ms */, 10000 /* 10 ms */);
BluetoothHidDeviceAppQosSettings outQos = new BluetoothHidDeviceAppQosSettings(
BluetoothHidDeviceAppQosSettings.SERVICE_GUARANTEED, 900, 9, 900,
10000 /* 10 ms */, 10000 /* 10 ms */);
boolean result = mHidDevice.registerApp(sdp, inQos, outQos, mCallback);HidConsts类的定义如下:
public class HidConsts {
public final static String NAME = "HID Device Testapp";
public final static String DESCRIPTION = "";
public final static String PROVIDER = "Codeaurora";
/* @formatter:off */
public final static byte[] DESCRIPTOR = {
(byte) 0x05, (byte) 0x01, // USAGE_PAGE (Generic Desktop)
(byte) 0x09, (byte) 0x02, // USAGE (Mouse)
(byte) 0xa1, (byte) 0x01, // COLLECTION (Application)
(byte) 0x09, (byte) 0x01, // USAGE (Pointer)
(byte) 0xa1, (byte) 0x00, // COLLECTION (Physical)
(byte) 0x85, (byte) 0x02, // REPORT_ID (2)
(byte) 0x05, (byte) 0x09, // USAGE_PAGE (Button)
(byte) 0x19, (byte) 0x01, // USAGE_MINIMUM (Button 1)
(byte) 0x29, (byte) 0x03, // USAGE_MAXIMUM (Button 3)
(byte) 0x15, (byte) 0x00, // LOGICAL_MINIMUM (0)
(byte) 0x25, (byte) 0x01, // LOGICAL_MAXIMUM (1)
(byte) 0x95, (byte) 0x03, // REPORT_COUNT (3)
(byte) 0x75, (byte) 0x01, // REPORT_SIZE (1)
(byte) 0x81, (byte) 0x02, // INPUT (Data,Var,Abs)
(byte) 0x95, (byte) 0x01, // REPORT_COUNT (1)
(byte) 0x75, (byte) 0x05, // REPORT_SIZE (5)
(byte) 0x81, (byte) 0x03, // INPUT (Cnst,Var,Abs)
(byte) 0x05, (byte) 0x01, // USAGE_PAGE (Generic Desktop)
(byte) 0x09, (byte) 0x30, // USAGE (X)
(byte) 0x09, (byte) 0x31, // USAGE (Y)
(byte) 0x15, (byte) 0x81, // LOGICAL_MINIMUM (-127)
(byte) 0x25, (byte) 0x7f, // LOGICAL_MAXIMUM (127)
(byte) 0x75, (byte) 0x08, // REPORT_SIZE (8)
(byte) 0x95, (byte) 0x02, // REPORT_COUNT (2)
(byte) 0x81, (byte) 0x06, // INPUT (Data,Var,Rel)
(byte) 0x09, (byte) 0x38, // USAGE (Wheel)
(byte) 0x15, (byte) 0x81, // LOGICAL_MINIMUM (-127)
(byte) 0x25, (byte) 0x7f, // LOGICAL_MAXIMUM (127)
(byte) 0x75, (byte) 0x08, // REPORT_SIZE (8)
(byte) 0x95, (byte) 0x01, // REPORT_COUNT (1)
(byte) 0x81, (byte) 0x06, // INPUT (Data,Var,Rel)
(byte) 0xc0, // END_COLLECTION
(byte) 0xc0, // END_COLLECTION
// battery strength
(byte) 0x05, (byte) 0x0c,
(byte) 0x09, (byte) 0x01,
(byte) 0xa1, (byte) 0x01,
(byte) 0x85, (byte) 0x20, // REPORT_ID (32)
(byte) 0x05, (byte) 0x01,
(byte) 0x09, (byte) 0x06,
(byte) 0xa1, (byte) 0x02,
(byte) 0x05, (byte) 0x06, // USAGE_PAGE (Generic Device Controls)
(byte) 0x09, (byte) 0x20, // USAGE (Battery Strength)
(byte) 0x15, (byte) 0x00, // LOGICAL_MINIMUM (0)
(byte) 0x26, (byte) 0xff, (byte) 0x00, // LOGICAL_MAXIMUM (100)
(byte) 0x75, (byte) 0x08, // REPORT_SIZE (8)
(byte) 0x95, (byte) 0x01, // REPORT_COUNT (1)
(byte) 0x81, (byte) 0x02, // INPUT (Data,Var,Abs)
(byte) 0xc0,
(byte) 0xc0,
(byte) 0x05, (byte) 0x01, // USAGE_PAGE (Generic Desktop)
(byte) 0x09, (byte) 0x06, // USAGE (Keyboard)
(byte) 0xa1, (byte) 0x01, // COLLECTION (Application)
(byte) 0x85, (byte) 0x01, // REPORT_ID (1)
(byte) 0x05, (byte) 0x07, // USAGE_PAGE (Keyboard)
(byte) 0x19, (byte) 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
(byte) 0x29, (byte) 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
(byte) 0x15, (byte) 0x00, // LOGICAL_MINIMUM (0)
(byte) 0x25, (byte) 0x01, // LOGICAL_MAXIMUM (1)
(byte) 0x75, (byte) 0x01, // REPORT_SIZE (1)
(byte) 0x95, (byte) 0x08, // REPORT_COUNT (8)
(byte) 0x81, (byte) 0x02, // INPUT (Data,Var,Abs)
(byte) 0x05, (byte) 0x0c, // USAGE_PAGE (Consumer Devices)
(byte) 0x15, (byte) 0x00, // LOGICAL_MINIMUM (0)
(byte) 0x25, (byte) 0x01, // LOGICAL_MAXIMUM (1)
(byte) 0x95, (byte) 0x07, // REPORT_COUNT (7)
(byte) 0x75, (byte) 0x01, // REPORT_SIZE (1)
(byte) 0x09, (byte) 0xb6, // USAGE (Scan Previous Track)
(byte) 0x09, (byte) 0xb5, // USAGE (Scan Next Track)
(byte) 0x09, (byte) 0xb7, // USAGE (Stop)
(byte) 0x09, (byte) 0xcd, // USAGE (Play/Pause)
(byte) 0x09, (byte) 0xe2, // USAGE (Mute)
(byte) 0x09, (byte) 0xe9, // USAGE (Volume Up)
(byte) 0x09, (byte) 0xea, // USAGE (Volume Down)
(byte) 0x81, (byte) 0x02, // INPUT (Data,Var,Abs)
(byte) 0x95, (byte) 0x01, // REPORT_COUNT (1)
(byte) 0x75, (byte) 0x01, // REPORT_SIZE (1)
(byte) 0x81, (byte) 0x03, // INPUT (Constant,Var,Abs)
(byte) 0x05, (byte) 0x07, // USAGE_PAGE (Keyboard)
(byte) 0x95, (byte) 0x05, // REPORT_COUNT (5)
(byte) 0x75, (byte) 0x01, // REPORT_SIZE (1)
(byte) 0x85, (byte) 0x01, // REPORT_ID (1)
(byte) 0x05, (byte) 0x08, // USAGE_PAGE (LEDs)
(byte) 0x19, (byte) 0x01, // USAGE_MINIMUM (Num Lock)
(byte) 0x29, (byte) 0x05, // USAGE_MAXIMUM (Kana)
(byte) 0x91, (byte) 0x02, // OUTPUT (Data,Var,Abs)
(byte) 0x95, (byte) 0x01, // REPORT_COUNT (1)
(byte) 0x75, (byte) 0x03, // REPORT_SIZE (3)
(byte) 0x91, (byte) 0x03, // OUTPUT (Cnst,Var,Abs)
(byte) 0x95, (byte) 0x06, // REPORT_COUNT (6)
(byte) 0x75, (byte) 0x08, // REPORT_SIZE (8)连接很简单,直接调用connect方法就可以了。
public void connect() {
if (mHidDevice == null) return;
mHidDevice.connect();
}3.4 BluetoothHidDeviceCallback
BluetoothHidDeviceCallback这个抽象类有7个回调方法,
private byte[] mBuffer = new byte[1];private final BluetoothHidDeviceCallback mCallback = new BluetoothHidDeviceCallback() {
@Override
public void onAppStatusChanged(BluetoothDevice pluggedDevice,
BluetoothHidDeviceAppConfiguration config, boolean registered) {
// 一般在registerApp和unregisterApp方法之后回调
// registered 表示是否注册上
}
@Override
public void onConnectionStateChanged(BluetoothDevice device, int state) {
// device 远程蓝牙设备 state连接状态
mBuffer = (byte) 63
mHidDevice.sendReport(32, mBuffer); // 不知道为啥子这样写?
}
// 其他5个方法就可以不管了。
@Override
public void onIntrData(byte reportId, byte[] data) {
Log.v(TAG, "intr data: reportId=" + reportId + " data=" + Arrays.toString(data));
}
@Override
public void onSetProtocol(byte protocol) {
Log.d(TAG, "protocol set to " + protocol);
}
@Override
public void onVirtualCableUnplug() {
}
@Override
public void onGetReport(byte type, byte id, int bufferSize) {
}
@Override
public void onSetReport(byte type, byte id, byte[] data) {
}
};BluetoothHidDeviceCallback7个方法都是C/C++等通过JNI机制的回调。
4,蓝牙鼠标
4.1 蓝牙鼠标滑动
实现鼠标在整个界面前后左右上下滑动
mTouchpad = view.findViewById(R.id.touchpad);
mTouchpad.setOnTouchListener(new OnTouchListener() {
private int mPrevX;
private int mPrevY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevX = (int) (event.getX() * mSpeed);
mPrevY = (int) (event.getY() * mSpeed);
break;
case MotionEvent.ACTION_MOVE:
int x = (int) (event.getX() * mSpeed);
int y = (int) (event.getY() * mSpeed);
mouseMove((byte) (x – mPrevX), (byte) (y - mPrevY));
mPrevX = x;
mPrevY = y;
break;
}
return true;
}
});private int mSpeed = 3;mSpeed的值控制鼠标移动的速度。
private byte[] mBuffer = new byte[4];
byte id = 2;public synchronized void move(byte dx, byte dy) {
// leave buttons state unchanged
mBuffer[1] = dx;
mBuffer[2] = dy;
mHidDevice.sendReport(id, mBuffer);
}4.2 蓝牙鼠标点击
将鼠标滑动到目标后,点击按钮可以选中目标。
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int which = 0;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mouseButtonDown(which);
break;
case MotionEvent.ACTION_UP:
mouseButtonUp(which);
break;
}
return false;
}
});Which的值有三种,分别是0,1,2 之间好像没什么差别。
public synchronized void buttonDown(int which) {
mBuffer[0] |= (1 << which);
mBuffer[1] = 0;
mBuffer[2] = 0;
mHidDevice.sendReport(id, mBuffer);
}
public synchronized void buttonUp(int which) {
mBuffer[0] &= ~(1 << which);
mBuffer[1] = 0;
mBuffer[2] = 0;
mHidDevice.sendReport(id, mBuffer);
}4.3 蓝牙鼠标翻页
比如在手机中,有时候界面不止一页,这样就需要翻页来显示了。
private int mScrollSpeed = 3; // 控制翻页的速度
mScrollZone.setOnTouchListener(new OnTouchListener() {
private int mPrevY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevY = (int) (event.getY() * mScrollSpeed);
break;
case MotionEvent.ACTION_MOVE:
int y = (int) (event.getY() * mScrollSpeed);
mouseScroll((byte) (mPrevY - y));
mPrevY = y;
break;
}
return true;
}
});
public synchronized void scroll(byte delta) {
mBuffer[3] = delta;
mHidDevice.sendReport(id, mBuffer);
mBuffer[3] = 0x00;
}5,蓝牙键盘
市面上的键盘类型较多,实现了3种蓝牙键盘,

标准键盘
lable以及对应keyCode如下:
<Keyboard>
<Layout>
<Key keyLabel="Esc" keyCode="0x29"/>
<Key keyLabel="F1" keyCode="0x3A"/>
<Key keyLabel="F2" keyCode="0x3B"/>
<Key keyLabel="F3" keyCode="0x3C"/>
<Key keyLabel="F4" keyCode="0x3D"/>
<Key keyLabel="F5" keyCode="0x3E"/>
<Key keyLabel="F6" keyCode="0x3F"/>
<Key keyLabel="F7" keyCode="0x40"/>
<Key keyLabel="F8" keyCode="0x41"/>
<Key keyLabel="F9" keyCode="0x42"/>
<Key keyLabel="F10" keyCode="0x43"/>
<Key keyLabel="F11" keyCode="0x44"/>
<Key keyLabel="F12" keyCode="0x45"/>
<Key keyLabel="Del" keyCode="0x4C"/>
</Layout>
<Layout>
<Key keyLabel="'" shiftLabel="~" keyCode="0x35"/>
<Key keyLabel="1" shiftLabel="!" keyCode="0x1E"/>
<Key keyLabel="2" shiftLabel="\@" keyCode="0x1F"/>
<Key keyLabel="3" shiftLabel="\#" keyCode="0x20"/>
<Key keyLabel="4" shiftLabel="$" keyCode="0x21"/>
<Key keyLabel="5" shiftLabel="%" keyCode="0x22"/>
<Key keyLabel="6" shiftLabel="^" keyCode="0x23"/>
<Key keyLabel="7" shiftLabel="&" keyCode="0x24"/>
<Key keyLabel="8" shiftLabel="*" keyCode="0x25"/>
<Key keyLabel="9" shiftLabel="(" keyCode="0x26"/>
<Key keyLabel="0" shiftLabel=")" keyCode="0x27"/>
<Key keyLabel="-" shiftLabel="_" keyCode="0x2D"/>
<Key keyLabel="=" shiftLabel="+" keyCode="0x2E"/>
<Key keyLabel="Backspace ←" keyCode="0x2A" weight="1.5"/>
</Layout>
<Layout>
<Key keyLabel="Tab ↹" keyCode="0x2B" weight="1.5"/>
<Key keyLabel="Q" keyCode="0x14"/>
<Key keyLabel="W" keyCode="0x1A"/>
<Key keyLabel="E" keyCode="0x08"/>
<Key keyLabel="R" keyCode="0x15"/>
<Key keyLabel="T" keyCode="0x17"/>
<Key keyLabel="Y" keyCode="0x1C"/>
<Key keyLabel="U" keyCode="0x18"/>
<Key keyLabel="I" keyCode="0x0C"/>
<Key keyLabel="O" keyCode="0x12"/>
<Key keyLabel="P" keyCode="0x13"/>
<Key keyLabel="[" keyCode="0x2F" shiftLabel="{"/>
<Key keyLabel="]" keyCode="0x30" shiftLabel="}"/>
<Key keyLabel="\\" keyCode="0x31" shiftLabel="|"/>
</Layout>
<Layout>
<Key keyLabel="Caps Lock" keyCode="0x39" weight="1.5"/>
<Key keyLabel="A" keyCode="0x04"/>
<Key keyLabel="S" keyCode="0x16"/>
<Key keyLabel="D" keyCode="0x07"/>
<Key keyLabel="F" keyCode="0x09"/>
<Key keyLabel="G" keyCode="0x0A"/>
<Key keyLabel="H" keyCode="0x0B"/>
<Key keyLabel="J" keyCode="0x0D"/>
<Key keyLabel="K" keyCode="0x0E"/>
<Key keyLabel="L" keyCode="0x0F"/>
<Key keyLabel=";" keyCode="0x33" shiftLabel=":"/>
<Key keyLabel="'" keyCode="0x34" shiftLabel="""/>
<Key keyLabel="Enter ↵" keyCode="0x28" weight="3.0"/>
</Layout>
<Layout>
<Key keyLabel="Shift ⇧" keyCode="0xE1" keyFunc="Shift" weight="1.5"/>
<Key keyLabel="Z" keyCode="0x1D"/>
<Key keyLabel="X" keyCode="0x1B"/>
<Key keyLabel="C" keyCode="0x06"/>
<Key keyLabel="V" keyCode="0x19"/>
<Key keyLabel="B" keyCode="0x05"/>
<Key keyLabel="N" keyCode="0x11"/>
<Key keyLabel="M" keyCode="0x10"/>
<Key keyLabel="," keyCode="0x36" shiftLabel="<"/>
<Key keyLabel="." keyCode="0x37" shiftLabel=">"/>
<Key keyLabel="/" keyCode="0x38" shiftLabel="\?"/>
<Key keyLabel="Shift ⇧" keyCode="0xE5" keyFunc="Shift" weight="1.5"/>
</Layout>
<Layout>
<Key keyLabel="Ctrl" keyCode="0xE0" weight="1.5"/>
<Key keyLabel="Win" keyCode="0xE3"/>
<Key keyLabel="Alt" keyCode="0xE2"/>
<Key keyLabel=" " keyCode="0x2C" weight="10.0"/>
<Key keyLabel="Alt Gr" keyCode="0xE6"/>
<Key keyLabel="Win" keyCode="0xE7"/>
<Key keyLabel="Menu" keyCode="0x76"/>
<Key keyLabel="Ctrl" keyCode="0xE4" weight="1.5"/>
</Layout>
</Keyboard> 左边是导航键盘 右边是数字键盘
导航键盘的lable以及对应keyCode如下:
<Keyboard>
<Layout>
<Key keyLabel="Vol Down ⇩" keyCode="0x86"/>
<Key keyLabel="Mute" keyCode="0x84"/>
<Key keyLabel="Vol Up ⇧" keyCode="0x85"/>
</Layout>
<Layout>
<Key keyLabel="Print Screen" keyCode="0x46"/>
<Key keyLabel="Scroll Lock" keyCode="0x47"/>
<Key keyLabel="Pause" keyCode="0x48"/>
</Layout>
<Layout>
<Key keyLabel="Insert" keyCode="0x49"/>
<Key keyLabel="Home" keyCode="0x4A"/>
<Key keyLabel="Page Up" keyCode="0x4B"/>
</Layout>
<Layout>
<Key keyLabel="Delete" keyCode="0x4C"/>
<Key keyLabel="End" keyCode="0x4D"/>
<Key keyLabel="Page Down" keyCode="0x4E"/>
</Layout>
<Layout>
<Key visible="false"/>
<Key keyLabel="↑" keyCode="0x52"/>
<Key visible="false"/>
</Layout>
<Layout>
<Key keyLabel="←" keyCode="0x50"/>
<Key keyLabel="↓" keyCode="0x51"/>
<Key keyLabel="→" keyCode="0x4F"/>
</Layout>
</Keyboard>数字键盘的lable以及对应keyCode如下:
<Keyboard>
<Layout>
<Key keyLabel="Prev\n⇦" keyCode="0x80"/>
<Key keyLabel="Next\n⇨" keyCode="0x81"/>
<Key keyLabel="Stop" keyCode="0x82"/>
<Key keyLabel="Play\nPause" keyCode="0x83"/>
<Key visible="false"/>
</Layout>
<Layout>
<Key keyLabel="Num\nLock" keyCode="0x53"/>
<Key keyLabel="/" keyCode="0x54"/>
<Key keyLabel="*" keyCode="0x55"/>
<Key keyLabel="-" keyCode="0x56"/>
</Layout>
<Layout>
<Key keyLabel="7\nHome" keyCode="0x5F"/>
<Key keyLabel="8 ↑" keyCode="0x60"/>
<Key keyLabel="9\nPgUp" keyCode="0x61"/>
<Key keyLabel="+" keyCode="0x57"/>
</Layout>
<Layout>
<Key keyLabel="4\n←" keyCode="0x5C"/>
<Key keyLabel="5" keyCode="0x5D"/>
<Key keyLabel="6\n→" keyCode="0x5E"/>
<Key visible="false"/>
</Layout>
<Layout>
<Key keyLabel="1\nEnd" keyCode="0x59"/>
<Key keyLabel="2 ↓" keyCode="0x5A"/>
<Key keyLabel="3\nPgDn" keyCode="0x5B"/>
<Key visible="false"/>
</Layout>
<Layout>
<Key keyLabel="0\nIns" keyCode="0x62" weight="2.0"/>
<Key keyLabel=".\nDel" keyCode="0x63"/>
<Key keyLabel="Enter" keyCode="0x58"/>
</Layout>
</Keyboard>按键事件都是一样的,主要就是keyCode不同,
@Override
public void onKeyUp(byte keyCode) {
keyboardKeyUp(keyCode);
}
@Override
public void onKeyDown(byte keyCode) {
keyboardKeyDown(keyCode);
}
private final static byte MODIFIER_BASE = (byte) 0xe0;
private final static byte MODIFIER_COUNT = 8; /* left ctrl -> right gui */
private byte[] mBuffer = new byte[8];
byte id = 1;
public synchronized void keyDown(byte key) {
if (key >= MODIFIER_BASE && key <= MODIFIER_BASE + MODIFIER_COUNT) {
mBuffer[0] |= (1 << (key - MODIFIER_BASE));
} else if ((key & 0x80) != 0) {
mBuffer[1] |= (1 << (key & 0x07));
} else {
for (int i = 2; i < 8; i++) {
if (mBuffer[i] == 0x00) {
mBuffer[i] = key;
break;
}
}
}
mHidDevice.sendReport(id, mBuffer);
}
public synchronized void keyUp(byte key) {
if (key >= MODIFIER_BASE && key <= MODIFIER_BASE + MODIFIER_COUNT) {
mBuffer[0] &= ~(1 << (key - MODIFIER_BASE));
} else if ((key & 0x80) != 0) {
mBuffer[1] &= ~(1 << (key & 0x07));
} else {
for (int i = 2; i < 8; i++) {
if (mBuffer[i] == key) {
mBuffer[i] = 0x00;
break;
}
}
}
mHidDevice.sendReport(id, mBuffer);
}这样就可以了,很简单吧。
6,源码解析
客户端的BluetoothHidDevice和服务端的HidDevService都比较简单,很直接,完全没有拐弯抹角的地方, sendReport方法从客户端到服务端的过程如下,
回调的7个方法流程如下,以onAppStatusChanged回调为例,
7,小节
问题:1,回调的几个方法以及2个类还未弄清楚其作用。
2,客户端的BluetoothInputDevice以及客户端的HidService和HID协议的文件在一个文件夹下,那么这2个类的作用是什么呢?如何使用。