0%

控件中的Ripple标签,即对应一个RippleDrawable,当它被设置为一个控件的background属性时,控件在按下时,即会显示水波效果

自定义

没有边界的Ripple(Ripple With No Mask)

波纹的内直径就是控件的长或者宽,实现内容如下,不需要任何操作即可。

1
2
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight" />

有边界的Ripple(Ripple With Mask)

波纹不超过控件的显示范围,实现内容如下,需要增加一个@android:id/mask配置,配置中的颜色就是波纹的背景色,会与ripple颜色重叠显示。

1
2
3
4
5
6
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="@color/white" />
</item>
</ripple>

用图片作边界的Ripple(Ripple With Picture Mask)

波纹不超过图片的显示范围,实现内容如下,需要增加一个@android:id/mask配置,配置中的图片就是波纹的背景,会与ripple颜色重叠显示。

1
2
3
4
5
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item android:id="@android:id/mask" android:drawable="@drawable/icon">
</item>
</ripple>

用形状作边界的Ripple(Ripple With Shape Mask)

波纹不超过形状的显示范围,实现内容如下,需要增加一个@android:id/mask配置,配置中的形状就是波纹的背景,会与ripple颜色重叠显示。

  • shape.xml
    1
    2
    3
    4
    5
    6
    <shape xmlns:android="http://schemas.android.com/apk/res/android" 
    android:shape="rectangle">
    <solid android:color="#ff9d77"/>
    <corners
    android:bottomRightRadius="100dp"/>
    </shape>
    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="utf-8"?>
    <ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="#FF0000" >
    <item
    android:id="@android:id/mask"
    android:drawable="@drawable/shape"/>
    </ripple>

搭配selector作为Ripple(Ripple With Selector)

如果在一个ripple标签中,添加一个item,在item的内部写上标签,那么这个RippleDrawable在按下的时候,同时具有水波效果和selector指定的图层。

波纹不超过Selector的显示范围,实现内容如下,需要增加一个@android:id/mask配置,配置中的Selector就是波纹的背景,会与ripple颜色重叠显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#FF0000" >
<item>
<selector>
<item
android:drawable="@drawable/icon_p"
android:state_pressed="true">
</item>
<item
android:drawable="@drawable/icon_n"
android:state_pressed="false">
</item>
</selector>
</item>
</ripple>

系统默认

在控件中设置背景如下:

  • 有边界:
    1
    2
    3
    <ImageView
    android:background="?selectableItemBackground"
    />
  • 无边界:
    1
    2
    3
    <ImageView
    android:background="?selectableItemBackgroundBorderless"
    />

selectableItemBackground

文件位于:
/sdk/android/platforms/android-29/data/res/drawable/item_background_material.xml

文件内容如下:

1
2
3
4
5
6
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item android:id="@id/mask">
<color android:color="@color/white" />
</item>
</ripple>

文件中的mask参数,用来控制波纹动画的背景或者说是边界,这里波纹动画只会在mask中显示,也就达到动画不超过控件的目的。

selectableItemBackgroundBorderless

文件位于:
/sdk/android/platforms/android-29/data/res/drawable/item_background_borderless_material.xml
文件内容如下:

1
2
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight" />

文件中的没有mask参数,波纹动画效果会超过控件的大小。

colorControlHighlight(?attr/colorControlHighlight)

文件位于:
/sdk/android/platforms/android-29/data/res/color/ripple_material_light.xml这里的light与Theme.AppCompat.Light有关。
文件内容如下:

1
2
3
4
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="@dimen/highlight_alpha_material_light"
android:color="@color/foreground_material_light" />
</selector>

参考

where-can-i-found-definition-for-selectableitembackgroundborderless-reference-at

概述

此文主要是简单的探索一下蓝牙电话和蓝牙音乐相关协议栈,所以很多细节并未涉及。

一般而言,我们把某个协议的实现代码称为协议栈(protocol stack),要理解协议栈需要先了解蓝牙的核心系统架构,如下图:

也可简单的抽象为3层

  • **User Application(Host)**:User Application即应用层,也被称为Host,我们调用Bluetooth API就属于应用层,例如,BluetoothAdapter中提供的接口。
  • HCI (Host controller Interface):上层在调用蓝牙API时,不会直接操作蓝牙底层(Controller)相关接口,而是通过HCI下发对应操作的Command给Controller,然后底层执行命令后返回执行结果,即Controller发送Event给HCI,HCI再通知给应用层,HCI起到了一个中间层的作用。
  • Controller:Controller是在最底层,硬件驱动。

按照上面的定义,HCI与Host层中的协议实现就是蓝牙协议栈

HCI

BLUETOOTH SPECIFICATION Version 4.2 [Vol 2, Part E] page 400

HCI(HOST CONTROLLER INTERFACE):主机控制层接口,主要负责透过transport把协议栈的数据发送给蓝牙芯片,并且接受来自蓝牙芯片的数据,数据主要分为HCI COMMAND(HOST->CONTROLLER),HCI EVENT(HOST<-CONTROLLER),HCI ACL(HOST<->CONTROLLER),HCI SCO(这个有点些微差异,因为部分芯片的SCO数据不是透过TRANSPORT直接跟HOST沟通,而是通过特殊的引脚,PCM IN/OUT/SYNC/CLK脚来传输数据)

HOST层的几乎所有数据最后都会通过HCI,所以我们通过HCI的日志就可以了解蓝牙协议栈中每个协议,手机中可以通过开发者模式打开HCI日志开关,推荐使用wireshark或者Frontline ComProbe Protocol Analysis System来分析HCI日志,下面给出来的的日志都是wireshark的结果。

HCI日志由各种数据包组成,我们需要了解其中的两种数据包如下:

Command 数据包

HCI Command数据包格式如下,开头的Opcode是区分不同类型的命令的唯一标识,Opcode由OpCode Group Field (OGF) 和 **OpCode Command Field (OCF)**组成。根据OGF的值,可以将HCI commands进行分类。OpCode 的计算公式为:OpCode = OGF << 6 + OCF 。有了OpCode计算的方式,我们就可以通过OpCode过滤HCI log里面的指定类型的HCI Command。

一次HCI Command指令发送完成,通常两条数据包,一条是host给controller的指令,一条是controller给host的指令反馈,可以确定指令发送是成功的。

Event 数据包

HCI Event 数据包由controller发送给Host,其中可以是蓝牙设备的消息也可以是连接的蓝牙设备的消息。

设备连接

蓝牙设备状态

BLUETOOTH SPECIFICATION Version 4.2 [Vol 2, Part B] page 159

扫描设备然后与之建立连接是蓝牙工作流程的第一步,所以先要了解一下蓝牙的状态如下图:

蓝牙状态转换图中可以看出 STANDBY状体是蓝牙设备的默认状态。

Page:这个子状态就是我们通常称为的连接(寻呼),进行连接/激活对应的slave的操作我们就称为page。

Page scan:这个子状态是和page对应的,它就是等待被page的slave所处的状态,换句话说,若想被page到,我们就要处于page scan的状态。

inquiry:这就是我们通常所说的扫描状态,这个状态的设备就是去扫描周围的设备。

inquiry scan:这就是我们通常看到的可被发现的设备。体现在上层就是我们在android系统中点击设备可被周围什么发现,那设备就处于这样的状态。

逻辑链路传输协议

ACL

BLUETOOTH SPECIFICATION Version 4.2 [Vol 1, Part A] page 63

The asynchronous connection-oriented (ACL) logical transport is used to carry
LMP and L2CAP control signaling and best effort asynchronous user data. The
ACL logical transport uses a 1-bit ARQN/SEQN scheme to provide simple
channel reliability. Every active slave device within a piconet has one ACL
logical transport to the piconet master, known as the default ACL.

ACL全称BR/EDR Asynchronous Connection-oriented,定向连接,它既支持对称连接,也支持不对称连接(既可以一对一,也可以一对多)。主设备负责控制链路带宽,并决定微微网中的每个从设备可以占用多少带宽和连接的对称性。从设备只有被选中时才能传送数据。ACL链路也支持接收主设备发给微微网中所有从设备的广播消息。

建立ACL需要设备HCI之间进行的下面的操作,这个也与蓝牙状态息息相关。

开始扫描

  • Host:

    1
    LocalBluetoothManager.getInstance().getBluetoothAdapter().startScanning(true);
  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    #发送扫描指令
    > 103 4.351128 host controller HCI_CMD 9 Sent Inquiry
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Inquiry
    Command Opcode: Inquiry (0x0401)
    0000 01.. .... .... = Opcode Group Field: Link Control Commands (0x01)
    .... ..00 0000 0001 = Opcode Command Field: Inquiry (0x001)
    Parameter Total Length: 5
    LAP: 0x9e8b33
    Inquiry Length: 10 (12.8 sec)
    Num Responses: 0
    [Pending in frame: 104]
    [Command-Pending Delta: 0.846ms]
    [Response in frame: 429]
    [Command-Response Delta: 12811.07ms]

    #HCI反馈扫描指令状态
    > 104 4.351974 controller host HCI_EVT 7 Rcvd Command Status (Inquiry)
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Command Status
    Event Code: Command Status (0x0f)
    Parameter Total Length: 4
    Status: Pending (0x00)
    Number of Allowed Command Packets: 1
    Command Opcode: Inquiry (0x0401)
    [Command in frame: 103]
    [Response in frame: 429]
    [Command-Pending Delta: 0.846ms]
    [Pending-Response Delta: 12810.224ms]

    #HCI发送的扫描到的设备信息
    > 114 4.505582 controller host HCI_EVT 258 Rcvd Extended Inquiry Result
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Extended Inquiry Result
    Event Code: Extended Inquiry Result (0x2f)
    Parameter Total Length: 255
    Number of responses: 1
    BD_ADDR: XiaomiCo_77:c2:35 (00:00:00:00:00:00)
    Page Scan Repetition Mode: R1 (0x01)
    Reserved: 0x02
    Class of Device: 0x5a020c (Phone:Smartphone - services: Networking Capturing ObjectTransfer Telephony)
    .000 0000 0011 0100 = Clock Offset: 0x0034
    RSSI: -88 dBm
    Extended Inquiry Response Data
    Device Name: +My Phone1234567890123456789012
    16-bit Service Class UUIDs
    32-bit Service Class UUIDs
    128-bit Service Class UUIDs
    Unused

停止扫描

  • Host:

    1
    LocalBluetoothManager.getInstance().getBluetoothAdapter().stopScanning();
  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    > 85	2.202129	host	controller	HCI_CMD	4	Sent Inquiry Cancel
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Inquiry Cancel
    Command Opcode: Inquiry Cancel (0x0402)
    0000 01.. .... .... = Opcode Group Field: Link Control Commands (0x01)
    .... ..00 0000 0010 = Opcode Command Field: Inquiry Cancel (0x002)
    Parameter Total Length: 0
    [Response in frame: 86]
    [Command-Response Delta: 2.051ms]

    > 86 2.204180 controller host HCI_EVT 7 Rcvd Command Complete (Inquiry Cancel)
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Command Complete
    Event Code: Command Complete (0x0e)
    Parameter Total Length: 4
    Number of Allowed Command Packets: 1
    Command Opcode: Inquiry Cancel (0x0402)
    Status: Success (0x00)
    [Command in frame: 85]
    [Command-Response Delta: 2.051ms]

完成扫描

  • Host:

    1
    2
    3
    4
    5
    LocalBluetoothManager.getInstance().getEventManager().registerCallback(new BluetoothCallback() {
    @Override
    public void onScanningStateChanged(boolean started) {
    }
    });
  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    > 429	17.162198	controller	host	HCI_EVT	4	Rcvd Inquiry Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Inquiry Complete
    Event Code: Inquiry Complete (0x01)
    Parameter Total Length: 1
    Status: Success (0x00)
    [Command in frame: 103]
    [Pending in frame: 104]
    [Pending-Response Delta: 12810.224ms]
    [Command-Response Delta: 12811.07ms]

设备配对

配对
  • Host:

    1
    CachedBluetoothDevice.startPairing();
  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    #删除需要连接设备本地记忆的Link Key
    > 685 33.812149 host controller HCI_CMD 11 Sent Delete Stored Link Key
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Delete Stored Link Key
    Command Opcode: Delete Stored Link Key (0x0c12)
    0000 11.. .... .... = Opcode Group Field: Host Controller & Baseband Commands (0x03)
    .... ..00 0001 0010 = Opcode Command Field: Delete Stored Link Key (0x012)
    Parameter Total Length: 7
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Delete All Flag: Delete only Link Key for specified BD_ADDR (0x00)
    [Response in frame: 686]
    [Command-Response Delta: 2.147ms]

    #建立ACL连接
    > 687 33.814531 host controller HCI_CMD 17 Sent Create Connection
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Create Connection
    Command Opcode: Create Connection (0x0405)
    Parameter Total Length: 13
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Packet Type: 0xcc18, DH5, DM5, DH3, DM3, DH1, DM1
    Page Scan Repetition Mode: R1 (0x01)
    Page Scan Mode: Mandatory Page Scan Mode (0x00)
    .000 0010 0000 1000 = Clock Offset: 0x0208 (650 msec)
    1... .... .... .... = Clock_Offset_Valid_Flag: true (1)
    Allow Role Switch: Local device may be master, or may become slave after accepting a master slave switch. (0x01)
    [Pending in frame: 688]
    [Command-Pending Delta: 0.965ms]
    [Response in frame: 690]
    [Command-Response Delta: 1118.609ms]

    #建立ACL连接成功
    > 690 34.933140 controller host HCI_EVT 14 Rcvd Connect Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Connect Complete
    Event Code: Connect Complete (0x03)
    Parameter Total Length: 11
    Status: Success (0x00)
    Connection Handle: 0x0033
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Type: ACL connection (Data Channels) (0x01)
    Encryption Mode: Encryption Disabled (0x00)
    [Command in frame: 687]
    [Pending in frame: 688]
    [Pending-Response Delta: 1117.644ms]
    [Command-Response Delta: 1118.609ms]

    #改变当前设备的角色,这里当前设备为从(Slave),连接设备为主(Master)
    > 689 34.929964 controller host HCI_EVT 11 Rcvd Role Change
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Role Change
    Event Code: Role Change (0x12)
    Parameter Total Length: 8
    Status: Success (0x00)
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Role: Currently the Slave for specified BD_ADDR (0x01)

    #与设备建立连接之后,准备开始验证
    > 697 34.935615 host controller HCI_CMD 6 Sent Authentication Requested
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Authentication Requested
    Command Opcode: Authentication Requested (0x0411)
    Parameter Total Length: 2
    Connection Handle: 0x0033
    [Pending in frame: 699]
    [Command-Pending Delta: 0.917ms]
    [Response in frame: 743]
    [Command-Response Delta: 4291.694ms]

    #连接设备询问Link Key
    > 701 34.936976 controller host HCI_EVT 9 Rcvd Link Key Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Link Key Request
    Event Code: Link Key Request (0x17)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)

    #当前设备无Link Key,也就是未于连接设备配对过
    > 705 34.938603 host controller HCI_CMD 10 Sent Link Key Request Negative Reply
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Link Key Request Negative Reply
    Command Opcode: Link Key Request Negative Reply (0x040c)
    0000 01.. .... .... = Opcode Group Field: Link Control Commands (0x01)
    .... ..00 0000 1100 = Opcode Command Field: Link Key Request Negative Reply (0x00c)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    [Response in frame: 706]
    [Command-Response Delta: 0.87ms]

    #使用的配对方式是`简单安全配对`SSP(Simple Secure Pairng), 就是直接弹配对码

    #连接设备询问当前设备的IO能力
    > 707 34.939729 controller host HCI_EVT 9 Rcvd IO Capability Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - IO Capability Request
    Event Code: IO Capability Request (0x31)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)

    #回复当前设备的IO能力
    >708 34.939897 host controller HCI_CMD 13 Sent IO Capability Request Reply
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - IO Capability Request Reply
    Command Opcode: IO Capability Request Reply (0x042b)
    0000 01.. .... .... = Opcode Group Field: Link Control Commands (0x01)
    .... ..00 0010 1011 = Opcode Command Field: IO Capability Request Reply (0x02b)
    Parameter Total Length: 9
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    IO Capability: Display Yes/No (1)
    OOB Data Present: OOB Authentication Data Not Present (0)
    Authentication Requirements: MITM Protection Required - Dedicated Bonding. Use IO Capability To Determine Procedure, No Secure Connection (3)
    [Response in frame: 709]
    [Command-Response Delta: 0.811ms]

    ######其它参数同步

    #连接设备的IO能力,io能力参数为Display Yes/No (0x01),需要用户确认
    > 737 36.532369 controller host HCI_EVT 12 Rcvd IO Capability Response
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - IO Capability Response
    Event Code: IO Capability Response (0x32)
    Parameter Total Length: 9
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    IO Capability: Display Yes/No (0x01)
    OOB Data Present: OOB Authentication Data Not Present (0)
    Authentication Requirements: MITM Protection Required - Dedicated Bonding. Use IO Capability To Determine Procedure, No Secure Connection (3)

    #当前设备接收到配对码信息,Numeric Value就是显示的配对码
    > 738 37.066181 controller host HCI_EVT 13 Rcvd User Confirmation Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - User Confirmation Request
    Event Code: User Confirmation Request (0x33)
    Parameter Total Length: 10
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Numeric Value: 446514

    #回复连接设备已接收到配对码信息
    > 739 37.096306 host controller HCI_CMD 10 Sent User Confirmation Request Reply
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - User Confirmation Request Reply
    Command Opcode: User Confirmation Request Reply (0x042c)
    0000 01.. .... .... = Opcode Group Field: Link Control Commands (0x01)
    .... ..00 0010 1100 = Opcode Command Field: User Confirmation Request Reply (0x02c)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    [Response in frame: 740]
    [Command-Response Delta: 1.46ms]

    #连接设备SSP验证成功
    > 741 39.215747 controller host HCI_EVT 10 Rcvd Simple Pairing Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Simple Pairing Complete
    Event Code: Simple Pairing Complete (0x36)
    Parameter Total Length: 7
    Status: Success (0x00)
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)

    #连接设备的Link Key,需要保存,用于下次连接
    > 742 39.226970 controller host HCI_EVT 26 Rcvd Link Key Notification
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Link Key Notification
    Event Code: Link Key Notification (0x18)
    Parameter Total Length: 23
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Key: bc31fb070e91eec2f2296fbf9bc8b518
    Key Type: Unknown (0x08)

    #验证完成
    > 743 39.227309 controller host HCI_EVT 6 Rcvd Authentication Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Authentication Complete
    Event Code: Authentication Complete (0x06)
    Parameter Total Length: 3
    Status: Success (0x00)
    Connection Handle: 0x0033
    [Command in frame: 697]
    [Pending in frame: 699]
    [Pending-Response Delta: 4290.777ms]
    [Command-Response Delta: 4291.694ms]
被配对
  • Host:

    1
    2
    3
    4
    5
    LocalBluetoothManager.getInstance().getEventManager().registerCallback(new BluetoothCallback() {
    @Override
    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
    }
    });
  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    #请求ACL连接
    > 326 21.946869 controller host HCI_EVT 13 Rcvd Connect Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Connect Request
    Event Code: Connect Request (0x04)
    Parameter Total Length: 10
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Class of Device: 0x5a020c (Phone:Smartphone - services: Networking Capturing ObjectTransfer Telephony)
    Link Type: ACL connection (Data Channels) (0x01)

    #允许ACL连接
    > 327 21.947178 host controller HCI_CMD 11 Sent Accept Connection Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Accept Connection Request
    Command Opcode: Accept Connection Request (0x0409)
    Parameter Total Length: 7
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Role: Remain the Slave for this connection. The LM will NOT perform the role switch. (0x01)
    [Pending in frame: 328]
    [Command-Pending Delta: 0.685ms]
    [Response in frame: 329]
    [Command-Response Delta: 4.432ms]

    #ACL连接成功
    > 329 21.951610 controller host HCI_EVT 14 Rcvd Connect Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Connect Complete
    Event Code: Connect Complete (0x03)
    Parameter Total Length: 11
    Status: Success (0x00)
    Connection Handle: 0x0033
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Type: ACL connection (Data Channels) (0x01)
    Encryption Mode: Encryption Disabled (0x00)
    [Command in frame: 327]
    [Pending in frame: 328]
    [Pending-Response Delta: 3.747ms]
    [Command-Response Delta: 4.432ms]


    > 370 22.259461 controller host HCI_EVT 12 Rcvd IO Capability Response
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - IO Capability Response
    Event Code: IO Capability Response (0x32)
    Parameter Total Length: 9
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    IO Capability: Display Yes/No (0x01)
    OOB Data Present: OOB Authentication Data Not Present (0)
    Authentication Requirements: MITM Protection Required - Dedicated Bonding. Use IO Capability To Determine Procedure, No Secure Connection (3)

    > 371 22.259809 controller host HCI_EVT 9 Rcvd IO Capability Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - IO Capability Request
    Event Code: IO Capability Request (0x31)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)

    > 372 22.259982 host controller HCI_CMD 13 Sent IO Capability Request Reply
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - IO Capability Request Reply
    Command Opcode: IO Capability Request Reply (0x042b)
    Parameter Total Length: 9
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    IO Capability: Display Yes/No (1)
    OOB Data Present: OOB Authentication Data Not Present (0)
    Authentication Requirements: MITM Protection Required - Dedicated Bonding. Use IO Capability To Determine Procedure, No Secure Connection (3)
    [Response in frame: 373]
    [Command-Response Delta: 0.619ms]

    > 374 22.958223 controller host HCI_EVT 13 Rcvd User Confirmation Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - User Confirmation Request
    Event Code: User Confirmation Request (0x33)
    Parameter Total Length: 10
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Numeric Value: 804568

    > 375 23.069385 host controller HCI_CMD 10 Sent User Confirmation Request Reply
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - User Confirmation Request Reply
    Command Opcode: User Confirmation Request Reply (0x042c)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    [Response in frame: 376]
    [Command-Response Delta: 1.81ms]

    #连接设备SSP验证成功
    > 377 26.919540 controller host HCI_EVT 10 Rcvd Simple Pairing Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Simple Pairing Complete
    Event Code: Simple Pairing Complete (0x36)
    Parameter Total Length: 7
    Status: Success (0x00)
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)

    #连接设备的Link Key,需要保存,用于下次连接
    > 378 26.926691 controller host HCI_EVT 26 Rcvd Link Key Notification
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Link Key Notification
    Event Code: Link Key Notification (0x18)
    Parameter Total Length: 23
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Key: 2160acf6b7403a97050f64de72570b1d
    Key Type: Unknown (0x08)

设备连接

连接
  • Host:

    1
    CachedBluetoothDevice.conntect(true);
  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    > 1214	47.023224	host	controller	HCI_CMD	17	Sent Create Connection
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Create Connection
    Command Opcode: Create Connection (0x0405)
    Parameter Total Length: 13
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Packet Type: 0xcc18, DH5, DM5, DH3, DM3, DH1, DM1
    Page Scan Repetition Mode: R1 (0x01)
    Page Scan Mode: Mandatory Page Scan Mode (0x00)
    .011 1001 0100 0100 = Clock Offset: 0x3944 (18325 msec)
    1... .... .... .... = Clock_Offset_Valid_Flag: true (1)
    Allow Role Switch: Local device may be master, or may become slave after accepting a master slave switch. (0x01)
    [Pending in frame: 1215]
    [Command-Pending Delta: 1.458ms]
    [Response in frame: 1217]
    [Command-Response Delta: 1630.81ms]

    > 1216 48.646874 controller host HCI_EVT 11 Rcvd Role Change
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Role Change
    Event Code: Role Change (0x12)
    Parameter Total Length: 8
    Status: Success (0x00)
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Role: Currently the Slave for specified BD_ADDR (0x01)

    > 1217 48.654034 controller host HCI_EVT 14 Rcvd Connect Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Connect Complete
    Event Code: Connect Complete (0x03)
    Parameter Total Length: 11
    Status: Success (0x00)
    Connection Handle: 0x0033
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Type: ACL connection (Data Channels) (0x01)
    Encryption Mode: Encryption Disabled (0x00)
    [Command in frame: 1214]
    [Pending in frame: 1215]
    [Pending-Response Delta: 1629.352ms]
    [Command-Response Delta: 1630.81ms]

    > 1278 48.812485 host controller HCI_CMD 6 Sent Authentication Requested
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Authentication Requested
    Command Opcode: Authentication Requested (0x0411)
    Parameter Total Length: 2
    Connection Handle: 0x0033
    [Pending in frame: 1279]
    [Command-Pending Delta: 0.945ms]
    [Response in frame: 1283]
    [Command-Response Delta: 16.693ms]

    > 1280 48.813892 controller host HCI_EVT 9 Rcvd Link Key Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Link Key Request
    Event Code: Link Key Request (0x17)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)

    > 1281 48.814039 host controller HCI_CMD 26 Sent Link Key Request Reply
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Link Key Request Reply
    Command Opcode: Link Key Request Reply (0x040b)
    Parameter Total Length: 22
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Key: 2160acf6b7403a97050f64de72570b1d
    [Response in frame: 1282]
    [Command-Response Delta: 1.336ms]

    > 1282 48.815375 controller host HCI_EVT 13 Rcvd Command Complete (Link Key Request Reply)
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Command Complete
    Event Code: Command Complete (0x0e)
    Parameter Total Length: 10
    Number of Allowed Command Packets: 1
    Command Opcode: Link Key Request Reply (0x040b)
    Status: Success (0x00)
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    [Command in frame: 1281]
    [Command-Response Delta: 1.336ms]

    > 1283 48.829178 controller host HCI_EVT 6 Rcvd Authentication Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Authentication Complete
    Event Code: Authentication Complete (0x06)
    Parameter Total Length: 3
    Status: Success (0x00)
    Connection Handle: 0x0033
    [Command in frame: 1278]
    [Pending in frame: 1279]
    [Pending-Response Delta: 15.748ms]
    [Command-Response Delta: 16.693ms]

被连接
  • Host:

    1
    2
    3
    4
    5
    LocalBluetoothManager.getInstance().getEventManager().registerCallback(new BluetoothCallback() {
    @Override
    public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
    }
    });
  • Controller:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    > 1900	62.151696	controller	host	HCI_EVT	13	Rcvd Connect Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Connect Request
    Event Code: Connect Request (0x04)
    Parameter Total Length: 10
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Class of Device: 0x5a020c (Phone:Smartphone - services: Networking Capturing ObjectTransfer Telephony)
    Link Type: ACL connection (Data Channels) (0x01)

    > 1901 62.152042 host controller HCI_CMD 11 Sent Accept Connection Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Accept Connection Request
    Command Opcode: Accept Connection Request (0x0409)
    Parameter Total Length: 7
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Role: Remain the Slave for this connection. The LM will NOT perform the role switch. (0x01)
    [Pending in frame: 1902]
    [Command-Pending Delta: 0.748ms]
    [Response in frame: 1903]
    [Command-Response Delta: 4.455ms]

    > 1903 62.156497 controller host HCI_EVT 14 Rcvd Connect Complete
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Connect Complete
    Event Code: Connect Complete (0x03)
    Parameter Total Length: 11
    Status: Success (0x00)
    Connection Handle: 0x0032
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Type: ACL connection (Data Channels) (0x01)
    Encryption Mode: Encryption Disabled (0x00)
    [Command in frame: 1901]
    [Pending in frame: 1902]
    [Pending-Response Delta: 3.707ms]
    [Command-Response Delta: 4.455ms]

    > 1968 62.415419 controller host HCI_EVT 9 Rcvd Link Key Request
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Link Key Request
    Event Code: Link Key Request (0x17)
    Parameter Total Length: 6
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)

    > 1969 62.415629 host controller HCI_CMD 26 Sent Link Key Request Reply
    ---------------------------------------------------------------------------
    Bluetooth HCI Command - Link Key Request Reply
    Command Opcode: Link Key Request Reply (0x040b)
    Parameter Total Length: 22
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    Link Key: 2160acf6b7403a97050f64de72570b1d
    [Response in frame: 1971]
    [Command-Response Delta: 0.934ms]

    > 1971 62.416563 controller host HCI_EVT 13 Rcvd Command Complete (Link Key Request Reply)
    ---------------------------------------------------------------------------
    Bluetooth HCI Event - Command Complete
    Event Code: Command Complete (0x0e)
    Parameter Total Length: 10
    Number of Allowed Command Packets: 1
    Command Opcode: Link Key Request Reply (0x040b)
    Status: Success (0x00)
    BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
    [Command in frame: 1969]
    [Command-Response Delta: 0.934ms]

SCO

BLUETOOTH SPECIFICATION Version 4.2 [Vol 1, Part A] page 64

The synchronous connection-oriented (SCO) logical transport is a symmetric,
point-to-point transport between the master and a specific slave. The SCO
logical transport reserves slots on the physical channel and can therefore be
considered as a circuit-switched connection between the master and the slave.
SCO logical transports carry 64 kb/s of information synchronized with the
piconet clock. Typically this information is an encoded voice stream. Three
different SCO configurations exist, offering a balance between robustness,
delay and bandwidth consumption.

SCO全称BR/EDR Synchronous Connection-Oriented,利用保留时隙传送数据包。连接建立后,主设备和从设备可以不被选中就发送SCO数据包。SCO数据包既可以传送话音,也可以传送数据,但在传送数据时,只用于重发被损坏的那部分的数据。

L2CAP

BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part A] page 30

L2CAP(Logical Link Control and Adaptation Protocol):逻辑链路控制与适配协议,将ACL数据分组交换为便于高层应用的数据分组格式,并提供协议复用和服务质量交换等功能。

通过协议多路复用、分段重组操作和组概念,向高层提供面向连接的和无连接的数据服务,L2CAP还屏蔽了低层传输协议中的很多特性,使得高层协议应用开发人员可以不必了解基层协议而进行开发。

从架构图可以看出,L2CAP位于HCI与上层业务之间,在建立物理连接(ACL)之后,上层业务的通信和控制都由L2CAP协议与微微网(piconet )中其它蓝牙设备进行交互,类似HTTP协议的存在。

下图概括了两个设备L2CAP之间的连接过程:

SDP

BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part B] page 222

The service discovery mechanism provides the means for client applications to
discover the existence of services provided by server applications as well as
the attributes of those services. The attributes of a service include the type or
class of service offered and the mechanism or protocol information needed to
utilize the service.

全称是Service Discovery Protocol,服务发现协议,服务发现协议(SDP)为应用程序提供了一种方法来发现哪些服务可用,并确定这些可用服务的特征。

从上图可知蓝牙设备中同时存在一个Client和Server,通过L2CAP协议与别的设备时行交互,通过Service Search Attribute Request 命令查询对方设备支持的蓝牙服务,其工作流程如图:

通过HCI可以看到发现蓝牙服务过过程中的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
> 455	22.824346	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	SDP	29	Sent Service Search Attribute Request : L2CAP: Attribute Range (0x0000 - 0xffff) 
---------------------------------------------------------------------------
Frame 455: 29 bytes on wire (232 bits), 29 bytes captured (232 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP Protocol
PDU: Service Search Attribute Request (0x06)
Transaction Id: 0x0000
Parameter Length: 15
Service Search Pattern: L2CAP
Maximum Attribute Byte Count: 1008
Attribute ID List
Data Element: Sequence uint8 5 bytes
0011 0... = Data Element Type: Sequence (6)
.... .101 = Data Element Size: uint8 (5)
Data Element Var Size: 5
Data Value
Data Element: Unsigned Integer 4 bytes
0000 1... = Data Element Type: Unsigned Integer (1)
.... .010 = Data Element Size: 4 bytes (2)
Data Value
Attribute Range: 0x0000ffff
Attribute Range From: 0x0000
Attribute Range To: 0xffff
Continuation State: no (00)

> 471 22.896191 XiaomiCo_6e:38:c5 (PHONE) localhost () SDP 281 Rcvd Service Search Attribute Response
---------------------------------------------------------------------------
Frame 471: 281 bytes on wire (2248 bits), 281 bytes captured (2248 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth SDP Protocol
PDU: Service Search Attribute Response (0x07)
Transaction Id: 0x0001
Parameter Length: 267
Attribute List Byte Count: 264
Data Fragment
Continuation State: no (00)
[Reassembled Attribute List]
Attribute Lists [count = 14]
Data Element: Sequence uint16 1269 bytes
0011 0... = Data Element Type: Sequence (6)
.... .110 = Data Element Size: uint16 (6)
Data Element Var Size: 1269
Data Value
Attribute List [count = 4] (Generic Attribute Profile)
Attribute List [count = 4] (Generic Access Profile)
Attribute List [count = 6] (Headset Audio Gateway)
Attribute List [count = 8] (Handsfree Audio Gateway)
Attribute List [count = 8] (A/V Remote Control Target)
Attribute List [count = 7] (Audio Source)
Attribute List [count = 11] (PAN NAP)
Attribute List [count = 9] (PAN PANU)
Attribute List [count = 7] (Phonebook Access Server)
Attribute List [count = 10] (Message Access Server)
Attribute List [count = 4] (Xiaomi Inc.)
Attribute List [count = 5] (CustomUUID: Unknown)
Attribute List [count = 8] (OBEX Object Push)
Attribute List [count = 4] (Unknown)

蓝牙电话

PBAP(RFCOMM/OBEX)

RFCOMM协议提供了对L2CAP协议上的串行端口的模拟。该协议基于ETSI标准GSM 07.10。

OBEX:Object Exchange的简称,对象交换协议,来源于红外通讯协议,但又不局限于具体的传输方式,后来被蓝牙组织SIG吸纳其中部分并进行优化处理作为蓝牙协议中的OBEX层用于蓝牙设备间的文件数据传输,如蓝牙传输文件(OPP)、同步电话簿(PBAP)和同步短信(MAP)等场景下都是以OBEX协议组织相关数据进行传输的。

PBAP: Phone Book Access Profile的简称,电话本访问协议,用于同步手机这些具有电话本功能设备上的通讯录和通话记录等。

RFCOMM/OBEX和协议不属于蓝牙定义的规范 ,只是被蓝牙采纳,而PBAP协议包在OBEX协议之上封装而成。

PBAP虚拟文件夹架构

PBAP中通话数据文件是在虚拟文件夹架构下,访问通话数据时需要指定路径,下图为官方架构图:

其中vcf的文件名称意义如下:

建立连接/断开连接

只使用OBEX协议

连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
> 798	23.907353	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	OBEX	40	Sent Connect - Phone Book Access Profile
---------------------------------------------------------------------------
Frame 798: 40 bytes on wire (320 bits), 40 bytes captured (320 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth RFCOMM Protocol
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: ?]
.000 0000 = Opcode: Connect (0x00)
1... .... = Final Flag: True
Packet Length: 26
[Response in Frame: 803]
Version: 1.0 (0x10)
Flags: 0x00
Max. Packet Length: 65534
Headers
Target: Phone Book Access Profile
Header Id: Target (0x46)
Length: 19
Value: 796135f0f0c511d809660800200c9a66: Phone Book Access Profile


> 803 23.917550 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 45 Rcvd Success - Phone Book Access Profile
---------------------------------------------------------------------------
Frame 803: 45 bytes on wire (360 bits), 45 bytes captured (360 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth RFCOMM Protocol
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 31
[Request in Frame: 798]
Version: 1.0 (0x10)
Flags: 0x00
Max. Packet Length: 65534
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Who: Phone Book Access Profile
Header Id: Who (0x4a)
Length: 19
Value: 796135f0f0c511d809660800200c9a66: Phone Book Access Profile

断开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
> 1142	58.752933	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	OBEX	22	Sent Disconnect
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.000 0001 = Opcode: Disconnect (0x01)
1... .... = Final Flag: True
Packet Length: 8
[Response in Frame: 1164]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1

> 1164 59.162598 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 22 Rcvd Success
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 8
[Request in Frame: 1142]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1

设置访问虚拟文件夹路径

在[PBAP协议规范]的5.2 SetPhoneBook Function章节,使用OBEX协议SetPath命令,通过obex.opcode=0x05判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# $/
> 808 24.023528 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 27 Sent Set Path ""
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.000 0101 = Opcode: Set Path (0x05)
1... .... = Final Flag: True
Packet Length: 13
[Response in Frame: 810]
Flags: 0x02
.... ...0 = Go back one folder (../) first: False
.... ..1. = Do not create folder, if not existing: True
Constants: 0x00
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Name: ""
Header Id: Name (0x01)
Length: 3
Name:

> 810 24.065127 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 22 Rcvd Success
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 8
[Request in Frame: 808]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1

# $/telecom/
> 811 24.078180 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 43 Sent Set Path "telecom"

获取通讯录数量

在[PBAP协议规范]的5.3 PullvCardListing Function章节,通过obex.header=**<x-bt/vcard-listing>**判断。

手机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
> 808	24.023528	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	OBEX	27	Sent Set Path ""
> 811 24.078180 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 43 Sent Set Path "telecom"
# $/telecom/pb/
> 816 24.118267 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 70 Sent Get final "pb"
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /telecom]
.000 0011 = Opcode: Get (0x03)
1... .... = Final Flag: True
Packet Length: 56
[Response in Frame: 818]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Name: "pb"
Header Id: Name (0x01)
Length: 9
Name: pb
Type: "x-bt/vcard-listing"
Header Id: Type (0x42)
Length: 22
Type: x-bt/vcard-listing
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 17
Parameter: Order #排序规则: { Alphabetical | Indexed | Phonetical }
Parameter Id: Order (0x01)
Parameter Length: 1
Max List Count: Indexed (0x00)
Parameter: Search Attribute #搜索: {Name | Number | Sound } 与 SearchValue配合,默认为Name
Parameter Id: Search Attribute (0x03)
Parameter Length: 1
Search Attribute: Name (0x00)
Parameter: Max List Count #最大条目数
Parameter Id: Max List Count (0x04)
Parameter Length: 2
Max List Count: 0 (0x0000)
Parameter: List Start Offset #偏移量
Parameter Id: List Start Offset (0x05)
Parameter Length: 2
List Start Offset: 0 (0x0000)

> 818 24.332995 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 29 Rcvd Success
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /telecom]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 15
[Request in Frame: 816]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 7
Parameter: Phonebook Size
Parameter Id: Phonebook Size (0x08)
Parameter Length: 2
Phonebook Size: 71 (0x0047)

SIM1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
> 819	24.336294	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	OBEX	27	Sent Set Path ""
> 822 24.379640 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 27 Sent Set Path ""
> 825 24.430836 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 37 Sent Set Path "SIM1"
> 828 24.481559 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 43 Sent Set Path "telecom"
# $/SIM1/telecom/pb/
> 831 24.532312 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 70 Sent Get final "pb"

> 833 24.578831 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 29 Rcvd Success
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /SIM1/telecom]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 15
[Request in Frame: 831]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 7
Parameter: Phonebook Size
Parameter Id: Phonebook Size (0x08)
Parameter Length: 2
Phonebook Size: 1 (0x0001)

获取通讯录

在[PBAP协议规范]的5.3 PullvCardListing Function章节,通过obex.header=<x-bt/phonebook> 判断,分页获取vCard格式。

手机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
> 834	24.580643	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	OBEX	27	Sent Set Path ""
#分页请求获取通讯录,从第1条数据开始,每页最多50条
> 837 24.632264 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 97 Sent Get final "telecom/pb.vcf"
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.000 0011 = Opcode: Get (0x03)
1... .... = Final Flag: True
Packet Length: 83
[Response in Frame: 844]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Name: "telecom/pb.vcf"
Header Id: Name (0x01)
Length: 33
Name: telecom/pb.vcf
Type: "x-bt/phonebook"
Header Id: Type (0x42)
Length: 18
Type: x-bt/phonebook
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 24
Parameter: Max List Count #最大条目数,这里只读取50条
Parameter Id: Max List Count (0x04)
Parameter Length: 2
Max List Count: 50 (0x0032)
Parameter: List Start Offset #偏移量,这是从第1条开始读取
Parameter Id: List Start Offset (0x05)
Parameter Length: 2
List Start Offset: 1 (0x0001)
Parameter: Filter #用于指示所请求的vCard中包含的属性
Parameter Id: Filter (0x06)
Parameter Length: 8
Filter: 0x00000000
Filter: 0x008001af, vCard Version, Formatted Name, Structured Presentation of Name, Associated Image or Photo, Delivery Address, Telephone Number, Electronic Mail Address, Nickname
Parameter: Format #vCard文件格式版本,这是3.0
Parameter Id: Format (0x07)
Parameter Length: 1
Format: 3.0 (0x01)

#传输数据片段
> 839 24.764415 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 1004 Rcvd OBEX fragment
---------------------------------------------------------------------------
Frame 839: 1004 bytes on wire (8032 bits), 1004 bytes captured (8032 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth RFCOMM Protocol
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
[Reassembled OBEX in frame: 844]
Data (990 bytes)

...
#传输成功
> 844 24.794969 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 182 Rcvd Success (x-bt/phonebook)
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
[6 OBEX Fragments (5117 bytes): #839(990), #840(990), #841(990), #842(990), #843(990), #844(167)] #6个数据片段,需要拼接对应Frame
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 5117
[Request in Frame: 837]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
End Of Body
Header Id: End Of Body (0x49)
Length: 5109
Value: 424547494e3a56434152440d0a56455253494f4e3a332e300d0a4e3ae591a83be4baa6e6…
Line-based text data: x-bt/phonebook (320 lines)
BEGIN:VCARD\r\n
VERSION:3.0\r\n
N:张;三;;;\r\n
FN:张三\r\n
TEL;TYPE=CELL:22222222\r\n
END:VCARD\r\n
BEGIN:VCARD\r\n
VERSION:3.0\r\n
N:李四;;;;\r\n
FN:李四\r\n
TEL;TYPE=CELL:11111111\r\n
END:VCARD\r\n

#请求获取vCard 3.0格式,并从第51条数据开始,最多50条
> 847 24.875858 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 96 Sent Get final "telecom/pb.vcf"
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.000 0011 = Opcode: Get (0x03)
1... .... = Final Flag: True
Packet Length: 83
[Response in Frame: 850]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Name: "telecom/pb.vcf"
Header Id: Name (0x01)
Length: 33
Name: telecom/pb.vcf
Type: "x-bt/phonebook"
Header Id: Type (0x42)
Length: 18
Type: x-bt/phonebook
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 24
Parameter: Max List Count
Parameter Id: Max List Count (0x04)
Parameter Length: 2
Max List Count: 50 (0x0032)
Parameter: List Start Offset
Parameter Id: List Start Offset (0x05)
Parameter Length: 2
List Start Offset: 51 (0x0033)
Parameter: Filter
Parameter Id: Filter (0x06)
Parameter Length: 8
Filter: 0x00000000
Filter: 0x008001af, vCard Version, Formatted Name, Structured Presentation of Name, Associated Image or Photo, Delivery Address, Telephone Number, Electronic Mail Address, Nickname
Parameter: Format
Parameter Id: Format (0x07)
Parameter Length: 1
Format: 3.0 (0x01)

> 849 24.953425 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 1004 Rcvd OBEX fragment

# 刚刚通过`Sent Get final "pb"`获取手机通讯录只有71条,现在只剩下21,这次就可以传输完成。
> 850 24.960363 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 940 Rcvd Success (x-bt/phonebook)
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
[2 OBEX Fragments (1915 bytes): #849(990), #850(925)]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 1915
[Request in Frame: 847]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
End Of Body
Header Id: End Of Body (0x49)
Length: 1907
Value: 424547494e3a56434152440d0a56455253494f4e3a332e300d0a4e3ae4bd993be4bf8ae7…
Line-based text data: x-bt/phonebook (122 lines)
BEGIN:VCARD\r\n
VERSION:3.0\r\n
N:张;三;;;\r\n
FN:张三\r\n
TEL;TYPE=CELL:22222222\r\n
END:VCARD\r\n
BEGIN:VCARD\r\n
VERSION:3.0\r\n
N:李四;;;;\r\n
FN:李四\r\n
TEL;TYPE=CELL:11111111\r\n
END:VCARD\r\n

SIM1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> 866	25.021131	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	OBEX	107	Sent Get final "SIM1/telecom/pb.vcf"
#这里手机直接拒绝了
> 868 25.032728 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 25 Rcvd Success
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 11
[Request in Frame: 866]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
End Of Body
Header Id: End Of Body (0x49)
Length: 3
Value: <MISSING>

获取通话记录

在[PBAP协议规范]的5.3 PullvCardListing Function章节,通过obex.header=<x-bt/phonebook> 判断,分页获取vCard格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
> 869	25.071027	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	OBEX	27	Sent Set Path ""
> 882 25.125323 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 43 Sent Set Path "telecom"
# $/telecom/cch/
> 887 25.138796 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 72 Sent Get final "cch"
> 895 25.222650 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 29 Rcvd Success
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /telecom]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 15
[Request in Frame: 887]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 7
Parameter: Phonebook Size
Parameter Id: Phonebook Size (0x08)
Parameter Length: 2
Phonebook Size: 2177 (0x0881)

> 897 25.225643 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 27 Sent Set Path ""
#分页获取通话记录,从第0条开始,每页最多50条
> 919 25.279427 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 85 Sent Get final "telecom/cch.vcf"
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.000 0011 = Opcode: Get (0x03)
1... .... = Final Flag: True
Packet Length: 71
[Response in Frame: 935]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Name: "telecom/cch.vcf"
Header Id: Name (0x01)
Length: 35
Name: telecom/cch.vcf
Type: "x-bt/phonebook"
Header Id: Type (0x42)
Length: 18
Type: x-bt/phonebook
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 10
Parameter: Max List Count
Parameter Id: Max List Count (0x04)
Parameter Length: 2
Max List Count: 50 (0x0032)
Parameter: Format
Parameter Id: Format (0x07)
Parameter Length: 1
Format: 3.0 (0x01)
> 935 25.381127 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 132 Rcvd Success (x-bt/phonebook)
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
[8 OBEX Fragments (7048 bytes): #926(990), #927(990), #928(990), #929(990), #930(990), #931(990), #934(990), #935(118)]
.010 0000 = Response Code: Success (0x20)
1... .... = Final Flag: True
Packet Length: 7048
[Request in Frame: 919]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
End Of Body
Header Id: End Of Body (0x49)
Length: 7040
Value: 424547494e3a56434152440d0a56455253494f4e3a332e300d0a464e3a0d0a4e3a0d0a54…
Line-based text data: x-bt/phonebook (350 lines)
BEGIN:VCARD\r\n
VERSION:3.0\r\n
FN:\r\n
N:\r\n
TEL;TYPE=0:10086\r\n
X-IRMC-CALL-DATETIME;TYPE=DIALED:20220317T161710\r\n
END:VCARD\r\n
BEGIN:VCARD\r\n
VERSION:3.0\r\n
FN:\r\n
N:\r\n
TEL;TYPE=0:02310050\r\n
X-IRMC-CALL-DATETIME;TYPE=MISSED:20220316T160142\r\n
END:VCARD\r\n

#分页获取通话记录,从第50条开始,每页最多50条
936 25.423573 localhost () XiaomiCo_6e:38:c5 (PHONE) OBEX 89 Sent Get final "telecom/cch.vcf"
---------------------------------------------------------------------------
OBEX Protocol
[Profile: PBAP (4)]
[Current Path: /]
.000 0011 = Opcode: Get (0x03)
1... .... = Final Flag: True
Packet Length: 75
[Response in Frame: 947]
Headers
Connection Id: 1
Header Id: Connection Id (0xcb)
Connection ID: 1
Name: "telecom/cch.vcf"
Header Id: Name (0x01)
Length: 35
Name: telecom/cch.vcf
Type: "x-bt/phonebook"
Header Id: Type (0x42)
Length: 18
Type: x-bt/phonebook
Application Parameters
Header Id: Application Parameters (0x4c)
Length: 14
Parameter: Max List Count
Parameter Id: Max List Count (0x04)
Parameter Length: 2
Max List Count: 50 (0x0032)
Parameter: List Start Offset
Parameter Id: List Start Offset (0x05)
Parameter Length: 2
List Start Offset: 50 (0x0032)
Parameter: Format
Parameter Id: Format (0x07)
Parameter Length: 1
Format: 3.0 (0x01)
> 947 25.516478 XiaomiCo_6e:38:c5 (PHONE) localhost () OBEX 810 Rcvd Success (x-bt/phonebook)

HFP(RFCOMM)

HFP(Hands-free Profile),让蓝牙设备可以控制电话,如接听、挂断、拒接、语音拨号等,拒接、语音拨号要视蓝牙耳机及电话是否支持。

目前HFP的使用场景有车载蓝牙,耳机和PDA,定义了AG和HFP两种角色。
AG(Audio Gate)音频网关—音频设备输入输出网关
HF(Hands Free)免提—该设备作为音频网关的远程音频输入/输出机制,并可提供若干遥控功能。

在车载蓝牙中,手机侧是AG车载蓝牙侧是HF,在android源代码中,将AG侧称为HFP/AG,将HF侧称为HFPClient/HF。

AT命令

在HFP使用AT命令控制电话,其参考3GPP 27.007标准,命令规范为:

  • 每个命令行只有一个命令

  • AG侧默认不回显命令

  • AG使用冗长的格式返回结果

  • 以下字符将被用于AT命令和返回结果格式中

    • <cr> 表示回车,也就是\r字符

    • <lf>表示换行,也就是\n字符

  • 从HF发送到AG的命令格式是:<AT command> <cr>

  • 从AG返回给HF的OK命令格式是:<cr><lf>OK<cr><lf>

  • 从AG到HF的ERROR命令是:<cr><lf>ERROR<cr><lf>

  • 从AG到HF的结果命令格式是:<cr><lf><result code><cr><lf>

HFP协议复用的AT命令:

  • ATA:标准电话应答AT命令

  • ATDdd…dd;:用电话号码打电话

  • ATD>nnn…;:ATD扩展命令,记忆拨号

  • ERROR:错误指示符,语法,格式或者通信过程出错。

  • OK:命令的成功应答。

  • NO CARRIER, BUSY, NO ANSWER, DELAYED, BLACKLISTED:AT扩展命令,AG返回给HF。

  • RING:来电

  • AT+CCWA:calling waiting notification AT命令。AT+CCWA=[<n>[,<mode>[,<class>]]]

  • +CCWA:Call Waiting notification返回结果码。只有<number><type>参数对HFP有意义,<number>是由双引号及其中的文本串组成。<type>是支持的电话格式,有如下值:

    • 128~143:国家或国际格式,

    • 144~159:国际电话,包括国家码前缀。

    • 160-175:国家码

    • AT+CHLD:通话保持,多方处理。AT+CHLD=<n><n>值覆盖0, 1, 1<idx>, 2, 2<idx>, 3 and 4,说明:

      • 0:释放所有保持电话或者设置用户的忙等待
      • 1:释放正在通话的电话,接听保持或等待的电话
      • 1<idx>:释放<idx>标识的电话
      • 2:将所有活跃电话设置成保持并且接受其它电话。
      • 2<idx>:请求接受<idx>标识电话,让其它电话保持。
      • 3:增加一个保持电话到对话中
      • 4:连接连个电话并且断开两个电话的订阅。HF侧可选。
    • AT+CHLD=?:查询AG侧保持和多方会话。

  • AT+CHUP:标准的挂断命令。AG会结束通话,也可用于拒接来电。

  • AT+CIND:AT+CIND?获取AG indicators当前的状态,`AT+CIND=?获取AG支持的indicator以及它们的次序。

    • service: Service availability indication:
      • <value>=0 implies no service. No Home/Roam network available.
      • <value>=1 implies presence of service. Home/Roam network available.
    • call: Standard call status indicator:
      • <value>=0 means there are no calls in progress
      • <value>=1 means at least one call is in progress
    • callsetup: Bluetooth proprietary call set up status indicator4. Support for this indicator is optional
      for the HF. When supported, this indicator shall be used in conjunction with, and as an extension
      of the standard call indicator. Possible values are as follows:
      • <value>=0 means not currently in call set up.
      • <value>=1 means an incoming call process ongoing.
      • <value>=2 means an outgoing call set up is ongoing.
      • <value>=3 means remote party being alerted in an outgoing call.
    • callheld: Bluetooth proprietary call hold status indicator. Support for this indicator is mandatory for
      the AG, optional for the HF. Possible values are as follows:
      • 0= No calls held
      • 1= Call is placed on hold or active/held calls swapped
        (The AG has both an active AND a held call)
      • Call on hold, no active call
    • signal: Signal Strength indicator:
      • <value>= ranges from 0 to 5
    • roam: Roaming status indicator:
      • <value>=0 means roaming is not active
      • <value>=1 means a roaming is active
    • battchg: Battery Charge indicator of AG:
      • <value>=ranges from 0 to 5
  • +CIND:当前indicator的列表

  • AT+CLCC:列出当前电话命令,

  • +CLCC:当前call结果码,支持参数是

    • idx:表示建立连接顺序或者接听电话的数字(从1开始)。

    • dir:0(outgoing),1(incoming)

    • status:

      • 0=Active

      • 1=Held

      • 2=Dialing(outgoing calls only)

      • 3=Alerting(outgoing calls only)

      • 4=Incoming(incoming calls only)

      • 5=Waiting(incoming calls only)

      • 6 = Call held by Response and Hold

    • mode= 0 (Voice), 1 (Data), 2 (FAX)

    • mpty=

      • 0 - this call is NOT a member of a multi-party (conference) call
      • 1 - this call IS a member of a multi-party (conference) call
    • number (optional)

    • type (optional)

  • AT+COPS: AT+COPS=3,0将被HF发送给AG

  • AT+CMEE: 使能+CME ERROR: 结果码

  • +CME ERROR

    • +CME ERROR: 0 – AG failure
  • AT+CLIP: Calling Line Identification notification 使能命令,It enables/disables the Calling Line Identification notification unsolicited result code 。

  • +CLIP : Standard “Calling Line Identification notification” unsolicited result code.

  • AT+CMER :Standard event reporting activation/deactivation AT command.

  • +CIEV: “indicator events reporting”结果码。<ind>,<value> result code ,对应AT+CIND 返回的列表中的参数和值。

  • AT+VTS: DTMF生成命令。

  • AT+CNUM: HF相应+CNUM命令

    • AT+CNUM (Retrieve Subscriber Number Information)
    • AT+CNUM=? (Test Subscriber Number Information – Not Implemented)
  • +CNUM: 用于将“ Subscriber Number Information ”从AG发送到HF的标准响应。

HFP协议扩展的AT命令:

  • AT+BIA (Bluetooth Indicators Activation)
  • AT+BINP (Bluetooth INPut)
  • AT+BLDN (Bluetooth Last Dialed Number)
  • AT+BVRA (Bluetooth Voice Recognition Activation)
  • +BVRA (Bluetooth Voice Recognition Activation)
  • AT+BRSF (Bluetooth Retrieve Supported Features)
  • +BRSF (Bluetooth Retrieve Supported Features)
  • AT+NREC (Noise Reduction and Echo Canceling)
  • AT+VGM (Gain of Microphone)
  • AT+VGS (Gain of Speaker)
  • +VGM (Gain of Microphone)
  • +VGS (Gain of Speaker)
  • +BSIR (Bluetooth Setting of In-band Ring tone)
  • AT+BTRH (Bluetooth Response and Hold Feature)
  • +BTRH (Bluetooth Response and Hold Feature)
  • AT+BCC (Bluetooth Codec Connection)
  • AT+BCS (Bluetooth Codec Selection)
  • +BCS (Bluetooth Codec Selection)
  • AT+BAC (Bluetooth Available Codecs)
  • AT+BIND (Bluetooth HF Indicators Feature)
  • +BIND (Bluetooth HF Indicators Feature)
  • AT+BIEV (Bluetooth HF Indicators Feature)

建立服务连接

Upon a user action or an internal event, either the HF or the AG may initiate a Service Level Connection
establishment procedure.
A Service Level Connection establishment requires the existence of a RFCOMM connection, that is, a
RFCOMM data link channel between the HF and the AG.
Both the HF and the AG may initiate the RFCOMM connection establishment. If there is no RFCOMM
session between the AG and the HF, the initiating device shall first initialize RFCOMM.

连接过程如下:

  1. 支持能力交换
    首先HF发送AT+BRSF=< HF supported features >给AG,目的是首先通知AG其具有的能力,其次接收AG返回的其自身的BRSF能力。

  2. Codec协商
    如果HF支持Codec Negotiation特征,其会查看AG返回的BRSF中是否也支持该特性,如果都支持该特性,则HF将发送AT+BAC=< HF available codecs >命令给AG以告知其可用的codec。

  3. AG Indicator
    HF从AG接收到的BRSF,可以知道AG支持的Indicator,并按顺序拍好,这是因为根据3GPP 27.007规范,AG可以支持Hands-Free不支持的profile。HF使用AT+CIND=?测试命令接收AG支持的indicator以及它们的次序。
    当HF获得必须的Indicator和它们的次序,它将通过AT+CIND?命令取得AG端正在使用indicator的状态。
    当HF取得AG的indicator后,HF会使用AT+CMER使能AG的indicator状态跟新功能,AG会返回OK作为应答。当service,call或者call建立状态发生时,AG将发送和indicator相关的+CIEV结果码给HF。HF根据收到的+CIEV码来跟新其自身内部的indicator。
    AG侧会一直保持indicator状态跟新功能使能直到收到AT+CMER指示其关闭或者HF和AG端的Service Level Connection连接断开。
    当HF使能AG的indicator状态跟新,如果AG和HF都支持呼叫等待(Call waiting)和3方通信(3-way calling)。HF将发送AT+CHLD=?测试命令取得AG是如何支持这种功能的。如果HF或者AG其中之一不支持三方通信,AT+CHLD=?命令不会被发送。

  4. HF Indicator
    如果HF支持HF indicator,其会查看AG是否支持HF indicator。
    如果HF和AG支持HF indicator特性,HF将发送AT+BIND=< HF supported HF indicators >通知HF侧支持的indicator,AG以OK应答。
    当AG接收到HF告知的HF indicator特性,HF将发送AT+BIND=?请求AG侧支持的HF indicator。AG将会以+BIND和以OK结尾的应答。
    当HF接收到AG支持的HF indicator,HF将会发送AT+BIND?命令确定HF目前使能的HF indicator。AG将会一次或多次以+BIND应答和以OK结尾的应答。
    至此HF可能发送AT+BIEV命令告知AG其使能的HF indicator发生变化。
    AG可以使用+BIND使能或者禁止任何HF indicator。

  5. End of Service Level Connection

    • HF需要知道Service Level Connection被完全建立,这可以通过以下几个方式:

      • 当且仅当AG通过+BRSF命令告知HF其支持的"HF indicator",在HF收到AG通过AT+BIND?命令发来的其支持的"HF indicator"可认为完全建立。
      • 当且仅当SDP服务发现AH和AG双方均支持"Call waiting and 3-way calling",在HFAG通过AT+CHLD命令发来的其对呼叫等待和多方电话的支持,对这种情况,"HF indicator"不要设置该比特位,AG也不要在+BRSF命令中设置该比特位。
      • 在HF使用AT+CMER命令成功启用“Indicator status update”功能,对这种情况SDP服务不应该设置“Call waiting and 3-way calling”比特位。
        如果HF收到AG通过indicator指示当前有电话时,HF查询AG的接听和保持状态来判断是否是未接听电话。
    • 同样AG侧Service Level Connection完全建立也有几种情况:

      • 当且仅当HF indicator在HF被设置且AG侧支持的indicator已经通过+BRSF命令应答,则AG以**+BIND加OK结尾**的命令应答其使能的HF indicator时可认为Service Level Connection完全建立。

      • 当且仅当“Call waiting and 3-way calling”比特在HF和AG的SDP服务中被置位,在AG通过**+CHLD加OK结尾**命令成功响应其对呼叫保持和多方电话支持时SLC会被完全建立。对这种情况,+BRSF不应该设置该HF indicator比特位。

      • AG已成功响应OK到AT + CMER命令(启用“Indicator status update”功能。)当“Call waiting and 3-way calling”位时,应适用这种情况未在支持的功能位图中设置为HF或AG,以及“HF Indicators ”对于通过+ BRSF交换的HF或AG的支持功能位图中未设置位的位图命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#判断是否支持"Call waiting and 3-way calling"
#AG发现HF, hfP支持的协议,HF支持"Call waiting and 3-way calling"
> 596 17.850811 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) SDP 36 Rcvd Service Search Attribute Request : Handsfree: [Service Class ID List 0x0001] [Protocol Descriptor List 0x0004] [Bluetooth Profile Descriptor List 0x0009] [(HFP HS) Supported Features 0x0311]
> 597 17.851437 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) SDP 69 Sent Service Search Attribute Response
---------------------------------------------------------------------------
Bluetooth SDP Protocol
PDU: Service Search Attribute Response (0x07)
Transaction Id: 0x0000
Parameter Length: 55
Attribute List Byte Count: 52
Attribute Lists [count = 1]
Data Element: Sequence uint8 50 bytes
0011 0... = Data Element Type: Sequence (6)
.... .101 = Data Element Size: uint8 (5)
Data Element Var Size: 50
Data Value
Attribute List [count = 4] (Handsfree)
Data Element: Sequence uint16 47 bytes
0011 0... = Data Element Type: Sequence (6)
.... .110 = Data Element Size: uint16 (6)
Data Element Var Size: 47
Data Value
Service Attribute: Service Class ID List (0x1), value = Handsfree -> Generic Audio
Service Attribute: Protocol Descriptor List (0x4), value = L2CAP -> RFCOMM:2
Service Attribute: Bluetooth Profile Descriptor List (0x9), value = Handsfree 1.6
Service Attribute: (HFP HS) Supported Features (0x311), value = (EC and/or Nr Function) (Call Waiting or Three Way Calling) (CLI Presentation Capability) (Voice Recognition Activation) (Remote Volume Control) (Wide Band Speech)
Continuation State: no (00)

#HF发现AG, hfP支持的协议,AG不支持"Call waiting and 3-way calling",只支持"3-way calling"
> 637 18.017460 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) SDP 33 Sent Service Search Attribute Request : Handsfree Audio Gateway: [Service Class ID List 0x0001] [Bluetooth Profile Descriptor List 0x0009] [(HFP AG) Supported Features 0x0311]
> 644 18.024689 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) SDP 52 Rcvd Service Search Attribute Response
---------------------------------------------------------------------------
Bluetooth SDP Protocol
PDU: Service Search Attribute Response (0x07)
Transaction Id: 0x0000
Parameter Length: 38
Attribute List Byte Count: 35
Attribute Lists [count = 1]
Data Element: Sequence uint8 33 bytes
0011 0... = Data Element Type: Sequence (6)
.... .101 = Data Element Size: uint8 (5)
Data Element Var Size: 33
Data Value
Attribute List [count = 3] (Handsfree Audio Gateway)
Data Element: Sequence uint16 30 bytes
0011 0... = Data Element Type: Sequence (6)
.... .110 = Data Element Size: uint16 (6)
Data Element Var Size: 30
Data Value
Service Attribute: Service Class ID List (0x1), value = Handsfree Audio Gateway -> Generic Audio
Service Attribute: Bluetooth Profile Descriptor List (0x9), value = Handsfree 1.6
Service Attribute: (HFP AG) Supported Features (0x311), value = (Three Way Calling) (EC and/or Nr Function) (Voice Recognition Function) (Wide Band Speech)
Continuation State: no (00)

#HFP建立连接
#交换支持功能,AG和HF都不支持"HF indicator"
> 634 18.012482 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) HFP 26 Sent AT+BRSF=255
---------------------------------------------------------------------------
Bluetooth HFP Profile
[Role: HS - Headset (2)]
AT Stream: AT+BRSF=255\r
Command 0: +BRSF
Command Line Prefix: AT
Command: +BRSF (Bluetooth Retrieve Supported Features)
Type: Action Command (0x003d)
Parameters
HS supported features bitmask: 255
.... .... .... .... .... .... .... ...1 = EC and/or NR function: True
.... .... .... .... .... .... .... ..1. = Call waiting or 3-way calling: True
.... .... .... .... .... .... .... .1.. = CLI Presentation: True
.... .... .... .... .... .... .... 1... = Voice Recognition Activation: True
.... .... .... .... .... .... ...1 .... = Remote Volume Control: True
.... .... .... .... .... .... ..1. .... = Enhanced Call Status: True
.... .... .... .... .... .... .1.. .... = Enhanced Call Control: True
.... .... .... .... .... .... 1... .... = Codec Negotiation: True
.... .... .... .... .... ...0 .... .... = HF Indicators: False
.... .... .... .... .... ..0. .... .... = eSCO S4 (and T2) Settings Support: False
0000 0000 0000 0000 0000 00.. .... .... = Reserved: 0x000000
> 641 18.022446 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 28 Rcvd +BRSF: 871
---------------------------------------------------------------------------
Bluetooth HFP Profile
[Role: AG - Audio Gate (1)]
AT Stream: \r\n+BRSF: 871\r\n
Command 0: +BRSF
Command: +BRSF (Bluetooth Retrieve Supported Features)
Type: Response (0x003a)
Parameters
AG supported features bitmask: 871
.... .... .... .... .... .... .... ...1 = Three Way Calling: True
.... .... .... .... .... .... .... ..1. = EC and/or NR function: True
.... .... .... .... .... .... .... .1.. = Voice Recognition Function: True
.... .... .... .... .... .... .... 0... = In-band Ring Tone: False
.... .... .... .... .... .... ...0 .... = Attach Number to Voice Tag: False
.... .... .... .... .... .... ..1. .... = Ability to Reject a Call: True
.... .... .... .... .... .... .1.. .... = Enhanced Call Status: True
.... .... .... .... .... .... 0... .... = Enhanced Call Control: False
.... .... .... .... .... ...1 .... .... = Extended Error Result Codes: True
.... .... .... .... .... ..1. .... .... = Codec Negotiation: True
.... .... .... .... .... .0.. .... .... = HF Indicators: False
.... .... .... .... .... 0... .... .... = eSCO S4 (and T2) Settings Support: False
0000 0000 0000 0000 0000 .... .... .... = Reserved: 0x00000

#AG和HF都支持"Codec Negotiation",HF告知AG其可用的Codec编码(CVSD)
> 643 18.023696 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) HFP 23 Sent AT+BAC=1
---------------------------------------------------------------------------
Bluetooth HFP Profile
[Role: HS - Headset (2)]
AT Stream: AT+BAC=1\r
Command 0: +BAC
Command Line Prefix: AT
Command: +BAC (Bluetooth Available Codecs)
Type: Action Command (0x003d)
Parameters
Codec: CVSD (1)

#获取AG支持的indicator以及它们的次序
> 649 18.029340 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) HFP 24 Sent AT+CIND=?
> 652 18.035680 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 147 Rcvd +CIND: ("CALL",(0,1)),("CALLSETUP",(0-3)),("SERVICE",(0-1)),("SIGNAL",(0-5)),("ROAM",(0,1)),("BATTCHG",(0-5)),("CALLHELD",(0-2))

#获取AG indicators当前的状态
> 654 18.037547 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) HFP 23 Sent AT+CIND?
> 656 18.092460 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 38 Rcvd +CIND: 0,0,1,5,0,2,0

#标志HFP连接建立
#启用"Indicator status update"
> 658 18.093591 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) HFP 30 Sent AT+CMER=3,0,0,1
> 660 18.117156 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 20 Rcvd OK

HF拨打\挂断电话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#HF拨打电话
> 921 29.433324 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) HFP 24 Sent ATD10086;
---------------------------------------------------------------------------
Bluetooth HFP Profile
[Role: HS - Headset (2)]
AT Stream: ATD10086;\r
Command 0: D
Command Line Prefix: AT
Command: D (Dial)
[Expert Info (Warning/Protocol): Non mandatory type or command in this role]
Parameters: No
> 924 30.778650 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 20 Rcvd OK

#挂断电话
> 965 37.723080 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) HFP 22 Sent AT+CHUP
> 966 37.752000 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 20 Rcvd OK

HF获取通话电话列表

会HF会轮询此命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> 925	30.779082	SANYOEle_62:35:bb (DEVICE)	XiaomiCo_6e:38:c5 (PHONE)	HFP	22	Sent AT+CLCC 
> 929 30.820871 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 46 Rcvd +CLCC: 1,0,2,0,0,"10086",129
---------------------------------------------------------------------------
Bluetooth HFP Profile
[Role: AG - Audio Gate (1)]
AT Stream: \r\n+CLCC: 1,0,2,0,0,"10086",129\r\n
Command 0: +CLCC
Command: +CLCC (Current Calls)
Type: Response (0x003a)
Parameters
ID: 1
Direction: Mobile Originated (0)
State: Dialing (2)
Mode: Voice (0)
Mpty: Call is not one of multiparty (conference) call parties (0)
Number: "10086"
Type: The phone number format may be a national or international format, and may contain prefix and/or escape digits. No changes on the number presentation are required. (129)

AG下发通话状态

在开启"Indicator status update"(AT+CMER)之后,AG会主动下发当前的通话状态,Indicator Index对应+CIND返回的参数列表,Indicator <idx>对应其值。

拨打电话

1
2
#CALLSETUP=2,拨号中
> 1056 66.689969 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 27 Rcvd +CIEV: 2,2

无通话或者挂断

1
2
#CALL=0,无通话
1106 74.975552 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 27 Rcvd +CIEV: 1,0

来电

1
2
#CALLSETUP=1,来电中
> 1056 66.689969 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) HFP 27 Rcvd +CIEV: 2,1

建立SCO/eSCO连接

蓝牙电话的音频数据由SCO/eSCO协议传输,在HCI中无数据相关的日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#HF选择编解码格式
> 900 387.320548 XiaomiCo_6e:38:c5 (PHONE) localhost () HFP 24 Rcvd +BCS: 1
---------------------------------------------------------------------------
Bluetooth HFP Profile
[Role: AG - Audio Gate (1)]
AT Stream: \r\n+BCS: 1\r\n
Command 0: +BCS
Command: +BCS (Bluetooth Codec Selection)
Type: Response (0x003a)
Parameters
Codec: CVSD (1)
> 903 387.380459 localhost () XiaomiCo_6e:38:c5 (PHONE) HFP 23 Sent AT+BCS=1
---------------------------------------------------------------------------
Bluetooth HFP Profile
[Role: HS - Headset (2)]
AT Stream: AT+BCS=1\r
Command 0: +BCS
Command Line Prefix: AT
Command: +BCS (Bluetooth Codec Selection)
Type: Action Command (0x003d)
Parameters
Codec: CVSD (1)

#AG请求建立eSCO连接
> 906 387.530834 controller host HCI_EVT 13 Rcvd Connect Request
---------------------------------------------------------------------------
Bluetooth HCI Event - Connect Request
Event Code: Connect Request (0x04)
Parameter Total Length: 10
BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
Class of Device: 0x001f00 (Uncategorized: device code not specified:Unknown - services:)
Link Type: eSCO connection (Voice Channels) (0x02)

#HF允许建立eSCO连接
> 907 387.531078 host controller HCI_CMD 67 Sent Enhanced Accept Synchronous Connection Request
---------------------------------------------------------------------------
Bluetooth HCI Command - Enhanced Accept Synchronous Connection Request
Command Opcode: Enhanced Accept Synchronous Connection Request (0x043e)
0000 01.. .... .... = Opcode Group Field: Link Control Commands (0x01)
.... ..00 0011 1110 = Opcode Command Field: Enhanced Accept Synchronous Connection Request (0x03e)
Parameter Total Length: 63
BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
Tx Bandwidth (bytes/s): 8000
Rx Bandwidth (bytes/s): 8000
Transmit Coding Format
Receive Coding Format
Transmit Codec Frame Size: 60
Receive Codec Frame Size: 60
Input Bandwidth: 16000
Output Bandwidth: 16000
Input Coding Format
Output Coding Format
Input Coded Data Size: 16
Output Coded Data Size: 16
Input PCM Data Format: 2's complement (0x02)
Output PCM Data Format: 2's complement (0x02)
Input PCM Sample Payload MSB Position: 0
Output PCM Sample Payload MSB Position: 0
Input Data Path: Vendor Specific
Output Data Path: Vendor Specific
Input Transport Unit Size: 16
Output Transport Unit Size: 16
Max. Latency (ms): 12
Packet Type: 0x03bf, 3-EV5 may NOT be used, 2-EV5 may NOT be used, 3-EV3 may NOT be used, EV5 may be used, EV4 may be used, EV3 may be used, HV3 may be used, HV2 may be used, HV1 may be used
Retransmission Effort: At least 1 retransmission, optimize for link quality (2)
[Pending in frame: 909]
[Command-Pending Delta: 1.38ms]
[Response in frame: 910]
[Command-Response Delta: 3.862ms]

#eSCO连接成功
> 910 387.534940 controller host HCI_EVT 20 Rcvd Synchronous Connection Complete
---------------------------------------------------------------------------
Bluetooth HCI Event - Synchronous Connection Complete
Event Code: Synchronous Connection Complete (0x2c)
Parameter Total Length: 17
Status: Success (0x00)
Connection Handle: 0x0e00
BD_ADDR: XiaomiCo_6e:38:c5 (00:00:00:00:00:00)
Link Type: eSCO connection (0x02)
Transmit Interval: 12 slots (7.5 msec)
Retransmit Window: 2 slots (1.25 msec)
Rx Packet Length: 60
Tx Packet Length: 60
Air Mode: CVSD (2)
[Command in frame: 907]
[Pending in frame: 909]
[Pending-Response Delta: 2.482ms]
[Command-Response Delta: 3.862ms]

蓝牙音乐

AVDTP

AVDTP:AUDIO/VIDEO DISTRIBUTION TRANSPORT PROTOCOL(音视频分配传输协议) ,用于音乐数据流的建立与传输控制。

建立连接

1
> 458	11.574257	SANYOEle_62:35:bb (DEVICE)	XiaomiCo_6e:38:c5 (PHONE)	L2CAP	17	Sent Connection Request (AVDTP, SCID: 0x0042)

数据流传输

数据流的生命状态:

查找(Idle)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#查询AG的所有音源
> 468 11.652772 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) AVDTP 11 Sent Command - Discover
> 469 11.722467 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) AVDTP 15 Rcvd ResponseAccept - Discover - items: 2
---------------------------------------------------------------------------
Bluetooth AVDTP Protocol
Signal: Discover (ResponseAccept)
ACP SEP [1 - Audio Source] item 1/2
0000 01.. = SEID: 1
.... ..0. = In Use: False (0x0)
.... ...0 = RFA0: 0x0
0000 .... = Media Type: Audio (0x0)
.... 0... = Type: Source (0x0)
.... .000 = RFA1: 0x0
ACP SEP [2 - Audio Source] item 2/2
0000 10.. = SEID: 2
.... ..0. = In Use: False (0x0)
.... ...0 = RFA0: 0x0
0000 .... = Media Type: Audio (0x0)
.... 0... = Type: Source (0x0)
.... .000 = RFA1: 0x0

#获取音源详细信息
> 470 11.722896 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) AVDTP 12 Sent Command - GetAllCapabilities - ACP SEID [1 - Audio Source]
> 473 11.728705 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) AVDTP 23 Rcvd ResponseAccept - GetAllCapabilities - Audio SBC (44100 | Mono JointStereo | block: 4 8 12 16 | subbands: 8 | allocation: Loudness | bitpool: 2..53)
> 474 11.728960 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) AVDTP 12 Sent Command - GetAllCapabilities - ACP SEID [2 - Audio Source]
> 476 11.734811 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) AVDTP 25 Rcvd ResponseAccept - GetAllCapabilities - Audio MPEG-2,4 AAC
---------------------------------------------------------------------------
Bluetooth AVDTP Protocol
Signal: GetAllCapabilities (ResponseAccept)
0010 .... = Transaction: 0x2
.... 00.. = Packet Type: Single (0x0)
.... ..10 = Message Type: ResponseAccept (0x2)
00.. .... = RFA: 0x0
..00 1100 = Signal: GetAllCapabilities (0x0c)
Capabilities
Service: Media Transport
Service: Media Codec - Audio MPEG-2,4 AAC
Service: Delay Reporting

设置/打开(Configured/Open)

1
2
3
4
5
6
7
8
9
10
11
#选择对应音源
> 477 11.735508 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) AVDTP 25 Sent Command - SetConfiguration - ACP SEID [2 - Audio Source] - INT SEID [2 - Audio Source] - Audio MPEG-2,4 AAC
> 479 11.742210 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) AVDTP 11 Rcvd ResponseAccept - SetConfiguration

#打开音源
> 480 11.742538 SANYOEle_62:35:bb (DEVICE) XiaomiCo_6e:38:c5 (PHONE) AVDTP 12 Sent Command - Open - ACP SEID [2 - Audio Source]
---------------------------------------------------------------------------
Bluetooth AVDTP Protocol
Signal: Open (Command)
ACP SEID [2 - Audio Source]
> 485 11.747312 XiaomiCo_6e:38:c5 (PHONE) SANYOEle_62:35:bb (DEVICE) AVDTP 11 Rcvd ResponseAccept - Open

开始传输(Streaming)

1
2
> 601	28.945727	XiaomiCo_f2:31:c7 (PHONE)	SANYOEle_71:7b:5c (DEVICE)	AVDTP	12	Rcvd Command - Start - ACP SEID [2 - Audio Sink]
> 602 28.946025 SANYOEle_71:7b:5c (DEVICE) XiaomiCo_f2:31:c7 (PHONE) AVDTP 11 Sent ResponseAccept - Start

传输(Streaming)

使用RTP协议传输数据,AVDTP协议本身不处理数据流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
> 612	29.166322	XiaomiCo_f2:31:c7 (PHONE)	SANYOEle_71:7b:5c (DEVICE)	RTP	37	PT=Unknown, SSRC=0x2, Seq=1, Time=0
---------------------------------------------------------------------------
Frame 612: 37 bytes on wire (296 bits), 37 bytes captured (296 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth A2DP Profile
[ACP SEID: 2]
[INT SEID: 2]
[Codec: MPEG-2,4 AAC (0x02)]
[Stream Start in Frame: 612]
[Stream End in Frame: 22786]
[Stream Number: 1]
Real-Time Transport Protocol
[Stream setup by BT A2DP (frame 612)]
10.. .... = Version: RFC 1889 Version (2)
..0. .... = Padding: False
...0 .... = Extension: False
.... 0000 = Contributing source identifiers count: 0
0... .... = Marker: False
Payload type: Unknown (98)
Sequence number: 1
[Extended sequence number: 65537]
Timestamp: 0
Synchronization Source identifier: 0x00000002 (2)
Data (16 bytes)
Data: 47fc0000b0908003000621100520a41c
[Length: 16]

暂停(Streaming)

1
> 773	23.510436	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVDTP	11	Sent ResponseAccept - Suspend

释放(Closing)

1
> 5692	96.414176	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVDTP	12	Sent Command - Close - ACP SEID [2 - Audio Source]

AVCTP

**AVCTP: Audio/Video Control Transport Protocol(音频/视频控制传输协议)**,用于蓝牙设备间音乐数据流功能的发现与控制,但是控制的具体功能逻辑不由AVCTP处理,其只发送命令。

发现由SDP协议完成,然后发起L2CAP通道连接蓝牙设备之间的AVCTP服务,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#发现
> 134 0.964749 localhost () XiaomiCo_6e:38:c5 (PHONE) SDP 33 Sent Service Search Attribute Request : Audio Source: [Service Class ID List 0x0001] [Protocol Descriptor List 0x0004] [Bluetooth Profile Descriptor List 0x0009]
> 139 0.981837 XiaomiCo_6e:38:c5 (PHONE) localhost () SDP 64 Rcvd Service Search Attribute Response
---------------------------------------------------------------------------
Bluetooth SDP Protocol
PDU: Service Search Attribute Response (0x07)
Transaction Id: 0x0000
Parameter Length: 50
Attribute List Byte Count: 47
Attribute Lists [count = 1]
Data Element: Sequence uint8 45 bytes
0011 0... = Data Element Type: Sequence (6)
.... .101 = Data Element Size: uint8 (5)
Data Element Var Size: 45
Data Value
Attribute List [count = 3] (Audio Source)
Data Element: Sequence uint16 42 bytes
0011 0... = Data Element Type: Sequence (6)
.... .110 = Data Element Size: uint16 (6)
Data Element Var Size: 42
Data Value
Service Attribute: Service Class ID List (0x1), value = Audio Source
Attribute ID: Service Class ID List
Value
Service Attribute: Protocol Descriptor List (0x4), value = L2CAP:25 -> AVDTP (1.3)
Attribute ID: Protocol Descriptor List
Value
Service Attribute: Bluetooth Profile Descriptor List (0x9), value = Advanced Audio Distribution 1.3
Attribute ID: Bluetooth Profile Descriptor List
Value
Continuation State: no (00)


#连接
> 147 1.006617 localhost () XiaomiCo_6e:38:c5 (PHONE) L2CAP 17 Sent Connection Request (AVDTP, SCID: 0x0043)
> 155 1.017317 XiaomiCo_6e:38:c5 (PHONE) localhost () L2CAP 21 Rcvd Connection Response - Success (SCID: 0x0043, DCID: 0x0042)

AVRCP

**AVRCP:Audio/Video Remote Control Profile(音频/视频远程控制配置文件)**,用于控制蓝牙音乐的标准接口。

在AVRCP中,TG表示播放设备,CT表示控制设备。

命令格式

命令分为两个类型:VENDOR DEPENDENT 与PASS THROUGH,

  • VENDOR DEPENDENT:通过CtypePDU ID来区分功能,CtypeControl, Status , Notify,ACCEPTED,REJECTED, CHANGED,INTERIM,IMPLEMENTED,STABLE等。

  • PASS THROUGH:是播放控制命令,Ctype只有Control

其中请求命令CtypeNotify,回复事件CtypeInterim,事件通知CtypeChanged

获取TG播放器支持功能

PDU ID:GetFolderItems,通过Scope来区分功能,用于TG可以播放器列表与每个播放器支持的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
> 307	1.551666	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	29	Sent Browsing: Command - GetFolderItems - Scope: MediaPlayerList, StartItem: 0x0000, EndItem: 0x0013
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
PDU ID: GetFolderItems (0x71)
Parameter Length: 10
Scope: MediaPlayerList (0x00)
StartItem: 0
EndItem: 19
Attribute Count: All attributes are requested (0)
Attribute List

> 326 1.651062 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 67 Rcvd Browsing: OK - GetFolderItems - UidCounter: 0x0000, NumberOfItems: 1
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
PDU ID: GetFolderItems (0x71)
Parameter Length: 48
Status: OK (0x04)
UID Counter: 0x0000
Number Of Items: 1
Player: Music Player
Item Type: Media Player Item (0x01)
Item Length: 40
Player ID: 0
Major Player Type: Unknown (0x01)
Player SubType: Audio Book (0x00000001)
Play Status: Stopped (0x00)
Features
Not Used Features
.... ...1 = PASSTHROUGH Play: 0x1
.... ..1. = PASSTHROUGH Stop: 0x1
.... .1.. = PASSTHROUGH Pause: 0x1
...1 .... = PASSTHROUGH Rewind: 0x1
..1. .... = PASSTHROUGH FastForward: 0x1
1... .... = PASSTHROUGH Forward: 0x1
.... ...1 = PASSTHROUGH Backward: 0x1
.... .1.. = Advanced Control Player: 0x1
Character Set: UTF-8 (106)
Displayable Name Length: 12
Displayable Name: Music Player

获取TG支持事件

PDU ID:GetCapabilities,用于TG可以支持的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
> 288	1.233497	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	23	Sent Vendor dependent: Status - GetCapabilities(Events Supported)
> 299 1.252688 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 30 Rcvd Vendor dependent: Stable - GetCapabilities(Events Supported) - Count: 6
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 1100 = Ctype: Stable (0xc)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: GetCapabilities (0x10)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 8
Capability: Events Supported (0x03)
Capability Count: 0x06
Event ID: PlaybackStatusChanged (0x01)
Event ID: TrackChanged (0x02)
Event ID: PlaybackPositionChanged (0x05)
Event ID: AvailablePlayersChanged (0x0a)
Event ID: AddressedPlayerChanged (0x0b)
Event ID: NowPlayingContentChanged (0x09)
[Response Time: 19/1000ms]
[Command in frame: 288]

播放器状态

CT端获取TC端播放状态,Play Status的参数由GetFolderItems原语获取,通过Ctype来区分操作。

注册事件

PDU ID:RegisterNotification,Event ID为: PlaybackStatusChanged(GetCapabilities原语获取)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
> 300	1.252942	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	27	Sent Vendor dependent: Notify - RegisterNotification - PlaybackStatusChanged
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0011 = Ctype: Notify (0x3)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: RegisterNotification (0x31)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 5
Event ID: PlaybackStatusChanged (0x01)
Interval: 0
[Response Time: 107/1000ms]
[Response in frame: 324]

> 324 1.649874 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 24 Rcvd Vendor dependent: Interim - RegisterNotification - PlaybackStatusChanged - PlayStatus: Paused
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 1111 = Ctype: Interim (0xf)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: RegisterNotification (0x31)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 2
Event ID: PlaybackStatusChanged (0x01)
Play Status: Paused (0x02)
[Response Time: 107/1000ms]
[Command in frame: 300]

事件通知

PDU ID:RegisterNotification,Event ID为: PlaybackStatusChanged,通过Ctype来区分功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> 372	2.461590	XiaomiCo_6e:38:c5 (PHONE)	localhost ()	AVRCP	24	Rcvd Vendor dependent: Changed - RegisterNotification - PlaybackStatusChanged - PlayStatus: Stopped
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 1101 = Ctype: Changed (0xd)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: RegisterNotification (0x31)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 2
Event ID: PlaybackStatusChanged (0x01)
Play Status: Stopped (0x00)
[Response Time: 107/1000ms]
[Command in frame: 300]

主动获取

PDU ID:GetPlayStatus,往往与RegisterNotification-TrackChanged命令配合使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
> 466	18.051244	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	22	Sent Vendor dependent: Status - GetPlayStatus
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0001 = Ctype: Status (0x1)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: GetPlayStatus (0x30)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 0
[Response Time: 40/1000ms]
[Response in frame: 478]

> 478 18.091970 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 31 Rcvd Vendor dependent: Stable - GetPlayStatus PlayStatus: Playing, SongPosition: 6118ms, SongLength: 175000ms
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 1100 = Ctype: Stable (0xc)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: GetPlayStatus (0x30)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 9
Song Length: 175000
Song Position: 6118
Play Status: Playing (0x01)
[Response Time: 40/1000ms]
[Command in frame: 466]

播放进度

使用PDU ID:GetPlayStatus,获取TG端播放进度,CT端轮循调用此命令。其中Song Length参数为音乐长度,Song Position为当前播放进度。日志参考主动获取播放器章节。

音轨状态

CT端获取TC端播放器音轨状态,状态有下面两种:

  • 无音轨:Identifier: 0xffffffffffffffff (NOT SELECTED)
  • 有音轨:Identifier: 0x0000000000000000 (SELECTED)

音轨状态事件会在每次开始播放都会有事件,歌词就是通过发送音轨状态事件来完成的。

注册事件

PDU ID:RegisterNotification,Event ID为: TrackChanged(GetCapabilities原语获取)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
> 323	1.331254	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	27	Sent Vendor dependent: Notify - RegisterNotification - TrackChanged
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0011 = Ctype: Notify (0x3)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: RegisterNotification (0x31)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 5
Event ID: TrackChanged (0x02)
Interval: 0
[Response Time: 35/1000ms]
[Response in frame: 330]

> 330 1.366518 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 31 Rcvd Vendor dependent: Interim - RegisterNotification - TrackChanged - 0xFFFFFFFFFFFFFFFF (NOT SELECTED)
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 1111 = Ctype: Interim (0xf)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: RegisterNotification (0x31)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 9
Event ID: TrackChanged (0x02)
Identifier: 0xffffffffffffffff (NOT SELECTED)
[Response Time: 35/1000ms]
[Command in frame: 323]

事件通知

PDU ID:RegisterNotification,Event ID为: TrackChanged,通过Ctype来区分功能。

1
> 451	18.026715	XiaomiCo_6e:38:c5 (PHONE)	localhost ()	AVRCP	31	Rcvd Vendor dependent: Changed - RegisterNotification - TrackChanged - 0x0000000000000000 (SELECTED)

获取播放音轨的属性

获取当前音轨,播放音乐的属性,如标题、歌手等信息。Attribute List请查看AVRCP协议规范第26章节,往往与RegisterNotification-TrackChanged命令配合使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
> 465	18.050915	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	59	Sent Vendor dependent: Status - GetElementAttributes - 0x0000000000000000 (PLAYING)
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0001 = Ctype: Status (0x1)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: GetElementAttributes (0x20)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 37
Identifier: 0x0000000000000000
Number of Attributes: 7
Attribute List
Attribute ID: Title (0x00000001)
Attribute ID: Artist (0x00000002)
Attribute ID: Album (0x00000003)
Attribute ID: Media Number (0x00000004)
Attribute ID: Total Number of Media (0x00000005)
Attribute ID: Genre (0x00000006)
Attribute ID: Playing Time (0x00000007)
[Response Time: 3/1000ms]
[Response in frame: 469]

> 469 18.054345 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 127 Rcvd Vendor dependent: Stable - GetElementAttributes - Title: "Demons"
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 1100 = Ctype: Stable (0xc)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Vendor dependent (0x00)
Company ID: 00:19:58 (Bluetooth SIG, Inc.)
PDU ID: GetElementAttributes (0x20)
0000 00.. = RFA: 0x00
.... ..00 = Packet Type: Single (0x0)
Parameter Length: 105
Number of Attributes: 7
Attribute Entries
Attribute [ Title]: Demons
Attribute [ Artist]: Imagine Dragons
Attribute [ Album]: Continued Silence
Attribute [ Media Number]: 10
Attribute [Total Number of Media]: 18
Attribute [ Genre]:
Attribute [ Playing Time]: 175000
[Response Time: 3/1000ms]
[Command in frame: 465]

播放器控制

CT控制TG播放器的相关命令,都是PASS THROUGH类型,通过Operation ID来区分功能,通过Ctype来区分操作。

播放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> 298	1.252592	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	17	Sent Pass Through: Control - PLAY (Pushed)
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0000 = Ctype: Control (0x0)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Pass Through (0x7c)
0... .... = State: Pushed (0x0)
.100 0100 = Operation ID: PLAY (0x44)
Data Length: 0x00
[Response Time: 14/200ms]
[Response in frame: 308]

>308 1.266992 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 17 Rcvd Pass Through: Accepted - PLAY (Pushed)

暂停

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> 430	7.424245	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	17	Sent Pass Through: Control - PAUSE (Pushed)
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0000 = Ctype: Control (0x0)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Pass Through (0x7c)
0... .... = State: Pushed (0x0)
.100 0110 = Operation ID: PAUSE (0x46)
Data Length: 0x00
[Response Time: 70/200ms]
[Response in frame: 434]

> 434 7.494377 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 17 Rcvd Pass Through: Accepted - PAUSE (Pushed)

下一曲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> 1163	40.755874	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	17	Sent Pass Through: Control - BACKWARD (Pushed)
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0000 = Ctype: Control (0x0)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Pass Through (0x7c)
0... .... = State: Pushed (0x0)
.100 1100 = Operation ID: BACKWARD (0x4c)
Data Length: 0x00
[Response Time: 15/200ms]
[Response in frame: 1168]

> 1168 40.771498 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 17 Rcvd Pass Through: Accepted - BACKWARD (Pushed)

上一曲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> 969	37.736799	localhost ()	XiaomiCo_6e:38:c5 (PHONE)	AVRCP	17	Sent Pass Through: Control - FORWARD (Pushed)
---------------------------------------------------------------------------
Bluetooth AVRCP Profile
0000 .... = Reserved: 0x0
.... 0000 = Ctype: Control (0x0)
0100 1... = Subunit Type: Panel (0x09)
.... .000 = Subunit ID: 0x0
Opcode: Pass Through (0x7c)
0... .... = State: Pushed (0x0)
.100 1011 = Operation ID: FORWARD (0x4b)
Data Length: 0x00
[Response Time: 25/200ms]
[Response in frame: 976]

> 976 37.762691 XiaomiCo_6e:38:c5 (PHONE) localhost () AVRCP 17 Rcvd Pass Through: Accepted - FORWARD (Pushed)

参考

阻塞与挂起

挂起(suspend)

是指在操作系统进程管理将前台的进程暂停(suspend)并转入后台的动作。将进程挂起可以让用户在前台执行其他的进程。挂起的进程通常释放除CPU以外已经占有的系统资源,如内存等。

阻塞(block)

正在执行的进程由于发生某时间(如I/O请求、申请缓冲区失败等)暂时无法继续执行。此时引起进程调度,OS把处理机分配给另一个就绪进程,而让受阻进程处于暂停状态,一般将这种状态称为阻塞状态。

两者的区别

挂起是一种主动行为,因此恢复也应该要主动完成;

阻塞则是一种被动行为,恢复交由系统完成。而且挂起队列在操作系统里可以看成一个,而阻塞队列则是不同的事件或资源(如信号量)就有自己的队列。

  • 运行中挂起,进入静止就绪状态,释放资源,自身对换到外存中,不释放锁,也不释放CPU,比如sleep();
  • 而运行中阻塞,进入活动阻塞状态,释放资源,释放锁,释放CPU,比如wait()。

也就是说挂起在运行状态中要比阻塞靠前,挂起是一直在内存中定期运行自己,检查是否可以跳出当前状态;阻塞是自己不在运行,需要第三方检查自己是否可以跳出当前状态,在一定条件下也会由操作系统将其移出内存保存到外存中。

Java中的线程与锁与其的关系

Java 的 Thread.State 中定义了线程的 6 种状态,分别如下:

  1. NEW 未启动的,不会出现在 dump 文件中 (可以通过 jstack命令查看线程堆栈)
  2. RUNNABLE 正在 JVM 中执行的
  3. BLOCKED 被阻塞,等待获取监视器锁进入 synchronized 代码块或者在调用Object.wait 之后重新进入 synchronized 代码块
  4. WAITING 无限期等待另一个线程执行特定动作后唤醒它,也就是调用Object.wait 后会等待拥有同一个监视器锁的线程调用 notify/notifyAll来进行唤醒
  5. TIMED_WAITING 有时限的等待另一个线程执行特定动作
  6. TERMINATED 已经完成了执行

Thread.sleep

也就是说这是挂起(suspend),作用域在线程,持有CPU。

Object.wait/synchronized

也就是说这是阻塞(block),作用域在锁对象,释放CPU。

wait/notify是通过 JVM里的park/unpark 机制来实现的,在Linux下这种机制又是通过pthread_cond_wait/pthread_cond_signal实现。

Lock

也就是说这是挂起(suspend),作用域在锁对象,持有CPU。

Lock 用的是乐观锁机制。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

参考

本人跟随Arch Linux官方安装指南操作过程的一些记录。

准备

下载ISO文件并启动到 Live 环境,注意安全启动(Secure Boot)的问题。

官方下载地址

验证启动模式(非必须)

请用下列命令列出 efivars 目录:

1
ls /sys/firmware/efi/efivars

目录存在,操作系统使用UEFI模式启动,反之使用传统模式启动(BIOS,CSM)。

网络连接

安装过程需要连接互联网,Live环境默认开启DHCP,其它方式可以参考连接到因特网

更新系统时间

1
timedatectl set-ntp true

建立硬盘分区

使用fdisk分区,可以查看这篇博文有详细的使用说明

查看分区列表

1
fdisk -l

其中/dev/sda代表第一块物理磁盘(/dev/sdb,/dev/sdc与之类推)

操作对应磁盘(/dev/sda)分区

1
2
3
4
5
6
fdisk /dev/sda
# 进入fdisk应用命令行:
g #如果没有分区表或者是MBR表先删除,创建一个新的空GPT分区表
n #创建新的分区
t #改变分区类型
w #保存并退出

新建分区之后需要指定分区类型,使用命令t {partition number },可以参考下表配置分区:

Mount point Partition Partition type GUID
/boot /dev/sda1 C12A7328-F81F-11D2-BA4B-00A0C93EC93B: EFI system partition
[SWAP] /dev/sda2 0657FD6D-A4AB-43C4-84E5-0933C84B4F4F: Linux swap
/home /dev/sda3 933AC7E1-2EB4-4F13-B844-0E14E2AEF915: Linux /home
/ /dev/sda4 4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709: Linux x86-64 root (/)

通过t命令可以查看更多类型,PgUp向上,PgDn向下,q退出。

格式化分区

1
2
3
4
mkfs.fat -F32 /dev/sda1 #EFI system partition(ESP)
mkswap /dev/sda2 #swap
mkfs.ext4 /dev/sda3 #/home
mkfs.ext4 /dev/sda4 #/

挂载分区

将根磁盘卷 挂载/mnt,例如:

1
2
3
4
5
6
mount /dev/sda4 /mnt #挂载/
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot #挂载ESP
mkdir /mnt/home
mount /dev/sda3 /mnt/home #挂载/home
swapon /dev/sda2 #挂载swap

安装系统

选择一个合适的镜像(可选)

非官方镜像列表

1
2
3
nano /etc/pacman.d/mirrorlist
---
Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch #文件第一排写入

安装配置

这几个包按照需求选择是否添加 nano, grub, efibootmgr, iwd

1
2
pacstrap /mnt base linux linux-firmware nano grub efibootmgr iwd
genfstab -U /mnt >> /mnt/etc/fstab #定义磁盘分区

通过Chroot进入安装系统

1
arch-chroot /mnt # 进入安装好的arch linux系统

在当前Live Linux中通过Chroot进入我们挂在到/mnt下面的Arch Linux系统中(也就是我们这次安装的系统),以下操作均在其中

时间/时区

1
2
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  #设置时区
hwclock --systohc #硬件时间设置为UTC

本地化

编辑/etc/locale.gen 选择 地区

1
2
3
nano /etc/locale.gen #移除需要的地区前的#号
---
locale-gen #更新配置

然后创建/etc/locale.conf 文件,并添加 LANG 变量

1
2
3
nano /etc/locale.conf
---
LANG=en_GB.UTF-8 #推荐使用,而不是zh_CN

hostname&hosts

1
2
3
4
5
6
7
8
9
10
11
#创建/etc/hostname文件
nano /etc/hostname
---
LHCOMPUTER #写入主机名称

#编辑/etc/hosts
nano /etc/hosts
---
127.0.0.1 localhost
::1 localhost
127.0.1.1 LHCOMPUTER.localdomain LHCOMPUTER #添加

设置Root用户密码

1
passwd

安装引导程序

前面的步骤已经基本完成对启动系统的配置,现在需要计算机引导启动系统,我选择使用GRUB,以下是在EFI 系统分区下的命令:

1
2
grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB
grub-mkconfig -o /boot/grub/grub.cfg

完成

输入 exit 或按 Ctrl+d 退出 Chroot 环境。
可选用 umount -R /mnt 手动卸载被挂载的分区。
最后执行 reboot 重启系统。

使用新系统前的配置

选择网络管理程序

系统默认已安装systemd-networkd,以下步骤配置并开启。

1
2
3
4
5
6
7
8
$ networkctl #获取有关网络链接的状态信息
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 ens33 ether routable unmanaged
3 wlp2s0 wlan off unmanaged
4 vmnet1 ether routable unmanaged
5 vmnet8 ether routable unmanaged
5 links listed.

其中ens33为有线网卡,wlp2s0为无线网卡。

配置有线

1
2
3
4
5
6
7
8
9
10
11
nano /etc/systemd/network/dhcp.network
---
[Match]
Name=ens33

[Network]
DHCP=true
#Address=192.168.1.20/24
#Gateway=192.168.1.1
#DNS=119.29.29.29
#DNS=8.8.8.8

配置无线

需要先安装iwd包,使用命令连接对应无线网络。

  1. 要进入交互式提示符(interactive prompt),执行:$ iwctl
  2. [iwd]# station device scan
  3. [iwd]# station device get-networks
  4. [iwd]# station device connect SSID,会提示输入密码

完成后,配置网络

1
2
3
4
5
6
7
nano /etc/systemd/network/wireless.network
---
[Match]
Name=wlp2s0

[Network]
DHCP=true

开启自动服务

1
2
systemctl enable --now systemd-networkd.service
systemctl enable --now systemd-resolved.service #DNS

添加新用户

1
2
3
groupadd liaoheng
useradd -m -g liaoheng liaoheng
passwd liaoheng

安装sudo

1
2
3
4
5
pacman -S sudo
nano /etc/sudoers#下面内容到文件末尾
---
Defaults targetpw
liaoheng ALL=(ALL) ALL

开机时打开 Num Lock

1
2
sudo pacman -S systemd-numlockontty
sudo systemctl enable --now numLockOnTty.service

软件仓库(可选)

可以登录liaoheng账户

1
sudo pacman -Syu

添加Arch Linux Chinese Community Repository,编辑/etc/pacman.conf,添加

1
2
3
4
sudo nano /etc/pacman.conf
---
[archlinuxcn]
Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch

导入PGP keys:

失败解决办法

1
sudo pacman -Syy && sudo pacman -S archlinuxcn-keyring

图形界面

GNOME

1
2
3
4
sudo pacman -S gnome gnome-extra gnome-tweak-tool
sudo pacman -S networkmanager
sudo systemctl enable NetworkManager
sudo systemctl enable --now gdm.service

参考

密度无关像素(density-independent pixels)

Android 设备不仅有不同的屏幕尺寸(手机、平板电脑、电视等),而且其屏幕也有不同的像素尺寸。也就是说,有可能一部设备的屏幕为每平方英寸 160 像素,而另一部设备的屏幕在相同的空间内可以容纳 480 像素。如果您不考虑像素密度的这些差异,系统可能会缩放图片(导致图片变模糊),或者图片可能会以完全错误的尺寸显示。

如果开发者只对屏幕分辨率进行适配,也就是说使用像素(px)作为度量单位,那么在同一分辨率下的布局,在不同像素密度的屏幕中的显示完全不同。为了应对这种情况,Android引入了密度无关像素(dp)来作为度量单位,但最终的渲染还是使用像素(px)为度量单位,dp是做为一种中间值的存在。

密度无关像素(dp)是一个虚拟像素单位,1 dp 约等于中密度屏幕(160dpi;“基准”密度)上的 1 像素。对于其他每个密度,Android 会将此值转换为相应的实际像素数。

Android支持多种多样的屏幕密度,但是开发者不可能对每种都进行适配,于是将屏幕密度按一定关系进行划分,开发者只适配同一区间的屏幕密度成为一种比较好的方案。Android的屏幕密度分级区间,系统会基于不同区间自行按对应倍数进行缩放(包括布局和图片),开发者也可以基于这些区间规制自行适配,区间如下:

密度限定符 说明 缩放倍数
ldpi 适用于低密度 (ldpi) 屏幕 (~ 120dpi) 的资源。(在120dpi左右,而非固定值,不同系统也是有差距的) 0.75(px=dp*0.75)
mdpi 适用于中密度 (mdpi) 屏幕 (~ 160dpi) 的资源(这是基准密度)。默认的layout,dimens也是基于此。 1.0(px=dp)
hdpi 适用于高密度 (hdpi) 屏幕 (~ 240dpi) 的资源。 1.5(px=dp*1.5)
xhdpi 适用于加高 (xhdpi) 密度屏幕 (~ 320dpi) 的资源。 2.0(px=dp*2)
xxhdpi 适用于超超高密度 (xxhdpi) 屏幕 (~ 480dpi) 的资源。 3.0(px=dp*3)
xxxhdpi 适用于超超超高密度 (xxxhdpi) 屏幕 (~ 640dpi) 的资源。 4.0(px=dp*4)
nodpi 适用于所有密度的资源。这些是与密度无关的资源。无论当前屏幕的密度是多少,系统都不会缩放以此限定符标记的资源。
tvdpi 适用于密度介于 mdpi 和 hdpi 之间的屏幕(约 213dpi)的资源。这不属于“主要”密度组。它主要用于电视,而大多数应用都不需要它。对于大多数应用而言,提供 mdpi 和 hdpi 资源便已足够,系统将视情况对其进行缩放。如果您发现有必要提供 tvdpi 资源,应按一个系数来确定其大小,即 1.33*mdpi。例如,如果某张图片在 mdpi 屏幕上的大小为 100px x 100px,那么它在 tvdpi 屏幕上的大小应该为 133px x 133px。 1.33(px=dp*1.33)

例如,你在中密度屏幕上的一个控件或者图片,大小为 48x48 像素,那么它在其他各种密度的屏幕上的大小应该为:

  • 36x36 (0.75x) - 低密度 (ldpi)
  • 48x48(1.0x 基准)- 中密度 (mdpi)
  • 72x72 (1.5x) - 高密度 (hdpi)
  • 96x96 (2.0x) - 超高密度 (xhdpi)
  • 144x144 (3.0x) - 超超高密度 (xxhdpi)
  • 192x192 (4.0x) - 超超超高密度 (xxxhdpi)

如何使用密度无关像素进行适配屏幕

系统适配

  • 图片文件48x48(res/drawable/ic_icon.png)
  • 布局文件(res/layout/activity_main.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_marginTop="@dimen/text_margin_top"
android:layout_marginEnd="@dimen/text_margin_end"
android:layout_marginStart="@dimen/text_margin_start"
android:layout_marginBottom="@dimen/text_margin_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name" />
</FrameLayout>
  • 资源尺寸文件(res/values/dimens.xml)
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="text_margin_top">15dp</dimen>//缩放之后的px=15*缩放倍数
<dimen name="text_margin_end">10dp</dimen>
<dimen name="text_margin_start">20dp</dimen>
<dimen name="text_margin_bottom">20px</dimen>//不会缩放px=20
</resources>

系统会根据屏幕密度分级区间自行按对应倍数进行缩放,在中密度 (mdpi)下text_margin_top像素为15,在超高密度 (xhdpi)下,text_margin_top像素为15x2.0,ic_icon.png像素为96x96。

自定义适配

系统的缩放比在一定条件下,是可以保证应用的显示正常,但是往往不一定那么理想,于是手动去设置尺寸,更为常见,就是能过将对应尺寸的资源放入对应dp文件夹来实现,如:

  • 要适配xhdpi,就将同名图片放入res/drawable-xhdp/ic_icon.png
  • 要适配xhdpi,就将同名文件放入res/layout-xhdpi/activity_main.xml
  • 要适配xhdpi,就将同名文件放入res/values-xhdpi/dimens.xml中。

如果使用dp会在相应屏幕密度分级区间自行按对应倍数进行缩放,也就是说相当于覆盖了默认的res/values/dimens.xml中的参数,然后进入系统适配(按对应倍数进行缩放),px则不会进行缩放,例如(res/values-xhdpi/dimens.xml):

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="text_margin_top">5dp</dimen> //缩放之后的px=10
<dimen name="text_margin_end">5dp</dimen>
<dimen name="text_margin_start">10px</dimen>//不会缩放px=10
<dimen name="text_margin_bottom">15px</dimen>
</resources>

当前应用如果适配的dpi不匹配设备时,系统会先找比设备dpi更大最近的以适配的dpi,然后是更小最近的,一直到mdpi也就是默认适配文件。例如:

  • 当前应用适配有hdpi、xxdpi、xxxdpi,设备的dpi为xhdpi,于是系统给应用分配的是xxdpi中的参数。
  • 当前应用适配有hdpi,设备的dpi为xhdpi,于是系统给应用分配的是hdpi中的参数。
  • 当前应用适配有ldpi,设备的dpi为xhdpi,于是系统给应用分配的是mdpi(也就是默认值)中的参数。

使用代码强行设置应用显示的屏幕密度(dpi)

这种方式可以比如只适配了xhdpi,想在其它的dpi中也按xhdpi进行绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private float sNoCompatDensity;
private float sNoCompatScaledDensity;

/**
* 强制设置dpi
*
* @param targetDP 密度屏幕(160,240,360...)
* @param isV 是否横屏
*/
private void setCustomDensity(int targetDP, boolean isV) {
DisplayMetrics appDisplayMetrics = getResources().getDisplayMetrics();
if (sNoCompatDensity == 0) {
sNoCompatDensity = appDisplayMetrics.density;
sNoCompatScaledDensity = appDisplayMetrics.scaledDensity;
}

float targetDensity;
if (isV) {
targetDensity =
(float) appDisplayMetrics.heightPixels / (appDisplayMetrics.heightPixels / (targetDP / 160F));
} else {
targetDensity =
(float) appDisplayMetrics.widthPixels / (appDisplayMetrics.heightPixels / (targetDP / 160F));
}
float targetScaledDensity = targetDensity * (sNoCompatScaledDensity / sNoCompatDensity);
int targetDensityDpi = (int) (160 * targetDensity);

appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
}

使用最小宽度限定符(smallestWidth)进行适配

屏幕的基本尺寸,由可用屏幕区域的最小尺寸指定。具体而言,设备的 smallestWidth 是屏幕可用高度和宽度的最小尺寸(您也可将其视为屏幕的“最小可能宽度”)。无论屏幕的当前方向如何,您均可使用此限定符确保应用界面的可用宽度至少为 dp。
例如,如果布局要求屏幕区域的最小尺寸始终至少为 600dp,则可使用此限定符创建布局资源 res/layout-sw600dp/。仅当可用屏幕的最小尺寸至少为 600dp(无论 600dp 表示的边是用户所认为的高度还是宽度)时,系统才会使用这些资源。最小宽度为设备的固定屏幕尺寸特征;即使屏幕方向发生变化,设备的最小宽度仍会保持不变。

该最小宽度的计算方式为:屏幕宽度(px)/屏幕密度缩放倍数。如你的屏幕为720x1280 xhdpi,那么720/2=360。

使用如下:res/values-sw360dp/dimens.xml,就是满足最小宽度(dp)为360dpi的屏幕进行适配,这种方式是分辨率+屏幕密度(dpi),算是现在对于多尺寸屏幕适配比较好的方案。

可用宽度(高度)限定符进行适配

您可能希望根据当前可用的宽度或高度来更改布局,而不是根据屏幕的最小宽度来更改布局。例如,如果您有一个双窗格布局,您可能希望在屏幕宽度至少为 600dp 时使用该布局,但屏幕宽度可能会根据设备的屏幕方向是横向还是纵向而发生变化。

使用最小宽度限定符的适配方式对于等比例屏幕效果比较好,如果是屏幕长宽比例较大时就不太适用,这时可以使用可用宽度(高度)符,这两种方案搭配几乎可以满足所有适配需求。

计算方式为与最小宽度限定符一样。

使用如下:res/values-w360dp/dimens.xml,就是满足宽度(dp)为360dpi的屏幕进行适配或者res/values-h720dp/dimens.xml就是满足高度(dp)为720dpi的屏幕进行适配。

相关其它

dp与px的转换规则

1
px = dp * (dpi / 160)

dpi:由上表可得,就是每平方英寸可显示的像素数量,如mdpi~240dpi。

设计图中的px与dp转换

在Photoshop中的**像素/英寸 (ppi)**与Android的屏幕密度(dpi)基本是一个概念,于是在设计图(320ppi)中一个控件的外边距为10px,由上面的转换规则可知dp=px/(dpi/160),这里就是10/(320/160)=5,也就是在xhdpi的布局中对应控件的外边距为5dp。

改变设备的屏幕密度(dpi)

获取设备屏幕分辨率:adb shell wm size获取设备屏幕密度(dpi):adb shell wm density设置屏幕密度(dpi) :adb shell wm density 320重置屏幕密度(dpi) :adb shell wm density reset

注意:虽然改变了系统的参数,但是系统判断自定义适配规制是无效的,会跳过当前dpi,但是最小宽度限定符是生效的。例如:

  • 当前应用适配有hdpi、xxdpi、xxxdpi,给设备设置的是hdpi,于是系统给应用分配的是xxdpi中的参数。
  • 当前应用适配有sw480dp,给设备(720p)设置的是hdpi,是正常的。

获取屏幕参数代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void getAndroiodScreenProperty() {
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels; // 屏幕宽度(像素)
int height = dm.heightPixels; // 屏幕高度(像素)
float density = dm.density; // 屏幕密度(0.75 / 1.0 / 1.5)
int densityDpi = dm.densityDpi; // 屏幕密度dpi(120 / 160 / 240)
// 屏幕宽度算法:屏幕宽度(像素)/屏幕密度
int screenWidth = (int) (width / density); // 屏幕宽度(dp)
int screenHeight = (int) (height / density);// 屏幕高度(dp)

Log.d(TAG, "屏幕宽度(像素):" + width);
Log.d(TAG, "屏幕高度(像素):" + height);
Log.d(TAG, "屏幕密度(0.75 / 1.0 / 1.5):" + density);
Log.d(TAG, "屏幕密度dpi(120 / 160 / 240):" + densityDpi);
Log.d(TAG, "屏幕宽度(dp):" + screenWidth);
Log.d(TAG, "屏幕高度(dp):" + screenHeight);
}

参考

准备工作

开发环境

  • Python 3.8.1
  • Windows 10

安装依赖

1
2
pip install PyQt5
pip install PyQtWebEngine

Python端

  1. 使用QWebChannelregisterObject("JsBridge名","JsBridge")方法注册回调

    • JsBridge名:在JavaScript中调用时使用的对象名称
    • JsBridge:被JavaScript调用的Python对象
  2. JsBridge 对象

    • 入参
    1
    2
    3
    @QtCore.pyqtSlot(str)
    def log(self, message):
    print(message)
    • 出参
    1
    2
    3
    @QtCore.pyqtSlot(result=str)
    def getName(self):
    return "hello"
    • 出入参
    1
    2
    3
    4
    @QtCore.pyqtSlot(str, result=str)
    def test(self, message):
    print(message)
    return "ok"

JavaScript端

  1. 在Html的<head>中添加

    1
    <script src='qrc:///qtwebchannel/qwebchannel.js'></script>
  2. 调用

    1
    2
    3
    4
    5
    6
    new QWebChannel(qt.webChannelTransport, function(channel) {
    channel.objects.pythonBridge.test("hello",function(arg) {
    console.log("Python message: " + arg);
    alert(arg);
    });
    });
  3. 调试(Chrome DevTools)

    1. 配置环境变量:QTWEBENGINE_REMOTE_DEBUGGING = port
    2. 使用Chromium内核的浏览器打开地址:http://127.0.0.1:port
    3. 使用PyCharm中可以在运行配置(Run/Debug Configurations)中的Environment variables中添加环境变量,用;号分隔,然后可以直接运行。

Demo

Python

  1. JsBridge

    1
    2
    3
    4
    5
    6
    7
    from PyQt5 import QtCore

    class JsBridge(QtCore.QObject):
    @QtCore.pyqtSlot(str, result=str)
    def test(self, message):
    print(message)
    return "ok"
  2. Application
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    from PyQt5 import QtCore
    from PyQt5 import QtWebEngineWidgets
    from PyQt5.QtCore import QDir
    from PyQt5.QtWebChannel import QWebChannel
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    from PyQt5.QtWidgets import *

    class TestWindow(QMainWindow):
    def __init__(self):
    super().__init__()
    self.webView = QWebEngineView()
    self.webView.settings().setAttribute(
    QtWebEngineWidgets.QWebEngineSettings.JavascriptEnabled, True)

    channel = QWebChannel(self.webView.page())
    self.webView.page().setWebChannel(channel)
    self.python_bridge = JsBridge(None)
    channel.registerObject("pythonBridge", self.python_bridge)
    layout = QVBoxLayout()
    layout.addWidget(self.webView)
    widget = QWidget()
    widget.setLayout(layout)
    self.setCentralWidget(widget)

    self.resize(900, 600)
    self.setWindowTitle('Test')
    qr = self.frameGeometry()
    cp = QDesktopWidget().availableGeometry().center()
    qr.moveCenter(cp)
    self.move(qr.topLeft())
    self.show()
    html_path = QtCore.QUrl.fromLocalFile(QDir.currentPath() + "/assets/index.html")
    self.webView.load(html_path)

    if __name__ == '__main__':
    app = QApplication(sys.argv)
    m = TestWindow()
    sys.exit(app.exec_())

JavaScript

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Test</title>
<script src='qrc:///qtwebchannel/qwebchannel.js'></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
</head>
<body>
<button id="test">test</button>
</body>
<script>
$(document).ready(function() {
new QWebChannel(qt.webChannelTransport, function(channel) {
$('#test').on('click', function() {
channel.objects.pythonBridge.test("hello",function(arg) {
console.log("Python message: " + arg);
alert(arg);
});
});
});
});
</script>
</html>

本文需要了解基本Android开发知识

环境

  1. JDK
  2. Node
  3. Watchman 可选 Windows问题比较多,不推荐安装
  4. Android SDK配置环境

配置

  1. 项目结构,创建相应文件

├── android
├── index.js
├── node_modules
├── package.json

  1. 编辑package.json文件添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"name": "[项目名]",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "16.3.1",
"react-native": "0.55.1",
"react-navigation": "1.5.2"
},
"devDependencies": {
"babel-jest": "22.4.3",
"babel-preset-react-native": "4.0.0",
"jest": "22.4.3",
"react-test-renderer": "16.3.1"
},
"jest": {
"preset": "react-native"
}
}

也可以使用命令创建官方的HelloWorld项目,然后拷贝相关配置文件,如下:

1
2
npm install -g create-react-native-app
create-react-native-app AwesomeProject
  1. 安装nodejs库,在根目录中执行命令:
1
2
npm install
npm install -g react-native-cli
  1. 编辑index.js文件添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';

export default class Launch extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.js
</Text>
<Text style={styles.instructions}>
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu
</Text>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

AppRegistry.registerComponent('ReactTest', () => Launch);
  1. android目录中创建app工程项目,然后在项目中集成React Native
  • [可选] 在settings.gradle文件中添加:

    1
    rootProject.name = '[项目名]'
  • gradle.properties文件中添加:

    1
    android.useDeprecatedNdk=true
  • 在根目录build.gradle文件中添加:

    1
    2
    3
    4
    5
    6
    7
    allprojects {
    repositories {
    maven {
    url "$rootDir/../node_modules/react-native/android"
    }
    }
    }
  • 在app目录build.gradle文件中添加:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    apply from: "../../node_modules/react-native/react.gradle"
    android {
    defaultConfig {
    ndk {
    abiFilters "armeabi-v7a", "x86"
    }
    }
    splits {
    abi {
    reset()
    enable false
    universalApk false // If true, also generate a universal APK
    include "armeabi-v7a", "x86"
    }
    }
    }
    dependencies {
    compile "com.facebook.react:react-native:+"
    }
  • 在app项目中创建MApplication.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
      public class MApplication extends Application implements ReactApplication{

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this){

    @Override
    public boolean getUseDeveloperSupport() {
    return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(new MainReactPackage());
    }

    @Override
    protected String getJSMainModuleName() {
    return "index";
    }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
    }
    }
  • 在app项目中创建MainActivity.java

    1
    2
    3
    4
    5
    6
    7
    8
    public class MainActivity extends ReactActivity {

    @Nullable
    @Override
    protected String getMainComponentName() {
    return "ReactTest";//与index.js中registerComponent对应
    }
    }
  • 修改app项目中AndroidManifest.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <application
    android:name=".MApplication">
    <activity android:name=".MainActivity">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity>
    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
    </application>

运行

  1. 通过命令行直接运行开发服务和app:
1
react-native run-android

如果使用Windows系统请使用方法1,方法2会在运行app时出现异常:java.io.IOException: Could not delete path

  1. 通过命令运行开发服务,然后手动运行app
1
2
npm start
#也可使用: react-native start

开发服务正常启动后如下,使用中不能关闭(如果安装Watchman,开发服务会在后台运行可以关闭当前窗口):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
\> [email protected] start x:\xxxx\React
\> node node_modules/react-native/local-cli/cli.js start

Scanning 564 folders for symlinks in x:\xxxx\React\node_modules (43ms)
┌──────────────────────────────────────────────────────────────────────────── ┐
│ Running packager on port 8081. │
│ │
│ Keep this packager running while developing on any JS projects. Feel │
│ free to close this tab and run your own packager instance if you │
│ prefer. │
│ │
│ https://github.com/facebook/react-native │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Looking for JS files in
X:\xxxx\React

React packager ready.

Loading dependency graph, done.

提示

Demo:ReactTest

新进入公司做智能家居设备,于是查寻了一下Android BLE的问题,这是比较全面的一片文章,简单翻译一下。

原文链接:http://www.slideshare.net/yeokm1/introduction-to-bluetooth-low-energy

Android issues (the past) BLE支持之前的问题

http://stackoverflow.com/questions/14118658/devices-with-android-4-2-jelly-bean-supported-bluetooth-low-energy-ble

  • Before Android 4.3 (July 2013) Android 4.3在2013年七月之前
    • Fragmentation hell 痛苦的碎片化
    • Proprietary Libraries by OEMs, Android <= 4.2 各大OEM厂商提供的私有库
      • Samsung (quite reliable)
      • HTC – buggy, unreliable
      • Motorola (reliable but conflicts with Android 4.3)
    • Architecture issues 架构上的问题
  • Testing issues 其它问题

Android issues (today) BLE支持之后的问题

http://stackoverflow.com/questions/17870189/android-4-3-bluetooth-low-energy-unstable

  1. OS fragmentation
  • 74.2% of Android devices support BLE 只有74.2%的设备支持
  • Few support peripheral mode: 35.3% minus Nexus 4, 5, 7 (2012/2013) 只有35.3%支持手机作为外围设备,只有5.0以上才支持。
  1. All callbacks from BLE APIs are not on UI thread. 所有的回调不是在UI线程

  2. APIs considered new, some functions are buggy 考虑使用新的API的话会有一些bug

  3. Frequent connection drops (< 5.0) 5.0之前频繁连接会导致连接无法使用

    参考:https://code.google.com/p/android/issues/detail?id=180440

  4. Max BLE connections: 最大连接数

  • Software cap in Bluedroid code: BTA_GATTC_CONN_MAX, GATT_MAX_PHY_CHANNEL
  • Android 4.3: 4
  • 4.4 - 5.0+: 7
  1. No API callback to indicate scanning has stopped 没有提供startLeScan()扫描结束的回调
  • Scan supposed to be indefinite by API specification, but some phones stop scan after some time API规范与实际扫描结果并不相符,并且一些手机停止扫描需要的时间比较长。

    比如有一些手机会一直回调设备,直到设备连接成功,而有一些设备(Samsung…)只回调一次

  • Known offender: Samsung 比如著名的三星

  • Solution: Restart scan at regular intervals 解决方法:扫描定时

  1. Different scan return result behaviours (See further reading) 扫描的结果并不相同
  • Some phones filter advertisement results, some phones do not. (usually on 4.3 and 4.4)有一些设备会过滤掉广告结果
  1. Bugs on (Samsung) phones at least < 5.0 三星5.0之前bug
  • Scan using service UUID filtering does not work -> no results returned 使用startLeScan()过滤UUID无法工作,没有返回结果
  • connectGatt() must be called from UI threadconnectGatt()必须在UI线程中调用
  1. Slow LE initial discovery and connection time 初始化查找与连接设备相当缓慢
  • HTC seems to have this issueHTC好像有这个问题

    华为,三星也会,其他没有测试过

  1. A high-level view on issues collated by Anaren 一些高层view问题的整理
  1. A more comprehensive list of issues has been collated by iDevicesInc 全面问题清单

个人推荐使用基于RxJava的库:https://github.com/Polidea/RxAndroidBle