致谢
首先感谢酷安各位小米设备拥有者的NFC数据的无私分享,目前设备互联的NFC协议已经基本解析完毕。
酷安名:@星逝之殇、@云鹤霄、@潮風漢韵、@ChsBuffer、@醋友29999999、@橙米、@Cp0204。
本文初期只解析了一碰妙享的NFC数据,目前已经可以涵盖一碰妙享(屏幕镜像,文件快传),小米音箱妙播,小米蓝牙音箱的协议。
从代码来看,以下解析内容已经基本覆盖了多数可能的NFC数据类型,如有错误和更新欢迎指出。
本文重写并更新了之前的内容,修复了一些错误的描述,将协议和样例分开便于各位查看。
应用版本
所有的分析过程建立在对HyperOS中提取出的APP上,一碰妙享在小米电脑管家4.0.0.533
上通过测试。
名称 | 包名 | 版本 |
---|---|---|
小米互联通信服务 | com.xiaomi.mi_connect_service |
3.1.453.10 |
投屏 | com.milink.service |
15.0.5.0.ceaac61.2919843 |
米家 | com.xiaomi.smarthome |
9.1.501 |
NFC服务 | com.android.nfc |
14 |
NFC工具
分析的内容已经被整合为一个安卓App,提供相关的NFC工具
项目开源地址:
NDEF解析
一条NDEF的Message可以包含多条Record数据,安卓默认以第一条为基础寻找接收NFC数据的应用。
NDEF的数据格式此处不再赘述,除了安卓自带一套方法外,也存在直接可用的库。
小米的NFC数据都是建立在NDEF格式上的,一碰妙享和小米音箱妙播的NDEF数据都只有一个Record。
Record 0
tnf: 4 [EXTERNAL]
type: b'com.xiaomi.mi_connect_service:externaltype'
payload: ... ...
或者小米碰碰贴2
Record 0
tnf: 4 [EXTERNAL]
type: b'com.xiaomi.smarthome:externaltype'
payload: ... ...
小米蓝牙音箱还有另一条Record数据,这个Record的作用是给非小米设备扫描到时主动弹出米家主页用的,所以可以忽略。
Record 1
tnf: 1 [WELL_KNOWN]
type: b'U' [URI]
payload: 04:67:2e:68:6f:6d:65:2e:6d:69:2e:63:6f:6d (https://g.home.mi.com)
小米碰碰贴2还有用于打开指定包名的应用的两条记录。
tnf: 4 [EXTERNAL]
type: b'android.com:pkg'
payload: 63:6F:6D:2E:78:69:61:6F:6D:69:2E:73:6D:61:72:74:68:6F:6D:65 (com.xiaomi.smarthome)
tnf: 4 [EXTERNAL]
type: b'android.com:pkg'
payload: 63:6F:6D:2E:78:69:61:6F:6D:69:2E:6D:69:5F:63:6F:6E:6E:65:63:74:5F:73:65:72:76:69:63:65 (com.xiaomi.mi_connect_service)
载荷数据分析
小米在原生的NFC服务(com.android.nfc
)基础上,增加了自有NDEF类型可以调用系统应用的功能,其他情况下均会弹出不同类型的通知,用户点击后才会实际发送NFC标签的广播或者执行相应操作。
其中,当NDEF类型为上文提到的externaltype
类型时,除了NDEF的Action之外,还会以vnd.android.nfc://ext/[type]
的Uri寻找Activity启动。
根据安卓官方文档,要处理这种类型的数据,就需要APP在Manifest
的activity
中声明以下内容:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="vnd.android.nfc" android:host="ext" android:pathPrefix="/[type]"/>
</intent-filter>
使用ADB命令在手机上搜索可以得到com.xiaomi.mi_connect_service/.nfc.NfcFieldOnPublisher
和com.xiaomi.smarthome/.nfctag.ui.NFCReadActivity
:
adb shell cmd package query-activities --components -d "vnd.android.nfc://ext/com.xiaomi.mi_connect_service:externaltype"
adb shell cmd package query-activities --components -d "vnd.android.nfc://ext/com.xiaomi.smarthome:externaltype"
com.xiaomi.mi_connect_service
包名的App,名称为小米互联通信服务
。
查看其中的组件,发现这个载荷使用了ProtoBuf的数据存储格式,在APP的资源文件中进行对比可以找到AttrProto.proto
文件。
com.xiaomi.smarthome
包名的App,名称为米家
,其中协议主体部分相同,不同之处会在后文分别介绍。
以下为ProtoBuf部分截取的片段:
syntax = "proto3";
package mi_connect_service;
message AttrAdvData {
int32 versionMajor = 1;
int32 versionMinor = 2;
bytes apps = 3;//已过时。请使用appIds替代。
bytes flags = 4;
string name = 5;
bytes idHash = 6;
int32 deviceType = 7;
int32 securityMode = 8;
repeated bytes appsData = 9;
repeated bytes supportSetting = 10;
repeated bytes currentSetting = 11;
string wifiMac = 12;
repeated int32 appIds = 13;//引入appIds之前使用的是apps
int32 commData = 14;
bool ziped = 15;
string wiredMac = 16;//有线网卡mac
string btMac = 17; //Bluetooth Device MAC
}
message AttrOps {
AttrAdvData advData = 1;
int32 sequenceId = 14;
}
载荷中的数据为AttrOps
格式,但是由于sequenceId
在样例中没有出现过并且NDEF解析时也从未使用过这个字段,所以可以直接看AttrAdvData
。
AttrAdvData
所有NFC标签中AttrAdvData
的deviceType
均为:15
所有NFC标签中AttrAdvData
的name
均为: “MI-NFCTAG”
appId
数字 | 名称 |
---|---|
1 | MI_SHARE |
2 | MI_PLAY |
62 | MI_ACCOUNT |
63 | MI_MOVER |
16382 | MI_TEST |
3 | MI_MIRROR |
16381 | MI_REMOTE_CONTROLLER |
16379 | MI_VOIP |
16377 | MI_VIDEO_RELAY |
16378 | MI_TAP |
16376 | MI_WATCH_CAMERA |
所有NFC标签中AttrAdvData
的appId
均为:MI_TAP (16378)
flags
数字 | 操作 |
---|---|
0 或 Others | 发送 com.xiaomi.aiot.nfc.message 本地广播,转发NDEF的Tag 和Record 数据,aiot 协议,V1版本协议 |
1 | 发送 com.xiaomi.nfc.action.TAG_DISCOVERED 广播,使用ProtoBuf中的appsData 数据(NfcTagAppData 类型),V2版本协议 |
3 | 发送 com.xiaomi.nfc.action.[ACTION] 自定义ACTION广播,使用ProtoBuf中的appsData 数据,Handoff NFC协议 |
flags=0x00
V1版本协议
目前样例中的AttrAdvData
版本为:
- versionMajor: 1
- versionMinor: 2
这个版本AttrAdvData
的idHash
=0x00
。
发送内部广播
Intent
- Action:
com.xiaomi.aiot.nfc.message
- Extra:
com.xiaomi.aiot.nfc.extra.adv
NdefRecord.getPayload() 格式为AttrAdvData
的byte[]
数据com.xiaomi.aiot.nfc.extra.tag
安卓NFC的Tag对象
若AttrAdvData
中的deviceType
不为15,则发送Action: com.xiaomi.aiot.nfc.scan.msg
的内部广播报错。
查找com.xiaomi.aiot.nfc.message
,可以发现使用NfcTagAppData
解析com.xiaomi.aiot.nfc.extra.adv
数据。
NfcTagAppData
位置 | 含义 |
---|---|
0 | 主版本号 |
1 | 次版本号 |
2 ~ 5 | 写入时间,32位字节的整数,Unix时间戳 |
6 | flag(目前没用到) |
7 | Record数量,至少存在一个否则就是空标签 |
目前此处版本号无特殊定义和限制。
Record目前有两种类型NfcTagDeviceRecord
和NfcTagActionRecord
。
NfcTagDeviceRecord
Record类型为:0x01
位置 | 含义 |
---|---|
0 | Record类型 |
1 ~ 2 | Record长度,16位整数 Short,整个长度而不只是后续数据长度 |
3 ~ 4 | 设备类型,16位整数 Short |
5 | flag(目前没用到) |
6 | 设备编号,无特殊定义 |
之后的数据为设备属性键值对
设备属性键值对
键值对用以下格式反复
位置 | 含义 |
---|---|
0 ~ 1 | 类型,16位整数 Short |
2 ~ 3 | 值长度,16位整数 Short,设为X |
4 ~ 4+X-1 |
值,byte[] |
DeviceAttribute 设备属性
-
当NDEF类型为
com.xiaomi.mi_connect_service:externaltype
时:值范围:
[0, 19)
数值 名称 数据类型 1 DEVICE_ATTR_WIFI_MAC_ADDRESS byte[]
2 DEVICE_ATTR_BLUETOOTH_MAC_ADDRESS byte[]
3 DEVICE_ATTR_NIC_MAC_ADDRESS byte[]
4 DEVICE_ATTR_IP_ADDRESS 5 DEVICE_ATTR_PORT_1 6 DEVICE_ATTR_PORT_2 7 DEVICE_ATTR_PORT_3 8 DEVICE_ATTR_ID_HASH byte[]
9 DEVICE_ATTR_DEVICE_TOKEN 10 DEVICE_ATTR_AUTH_TOKEN 11 DEVICE_ATTR_DEVICE_NAME 12 DEVICE_ATTR_DEVICE_TYPE 13 DEVICE_ATTR_APP_DATA byte[]
14 DEVICE_ATTR_USER_ENV_TOKEN 15 DEVICE_ATTR_SSID String
17 DEVICE_ATTR_PASSWORD String
18 DEVICE_ATTR_MODEL String
16 或 默认 “UnSupport Attribute” 注:当DeviceAttribute为DEVICE_ATTR_APP_DATA (13)时,其数据如果以
"mxD".getBytes()
开头,其中的数据与设备属性键值对的格式相同,最后将其并入所有的DeviceAttribute中。
否则可能直接存储二进制数组或者UTF-8字符串。 -
当NDEF类型为
com.xiaomi.smarthome:externaltype
时:当
NfcTagActionRecord
的Action=ACTION_IOT
数值 名称 数据类型 1 DEVICE_ID String
2 USER_MODEL String
3 NFC_EXTRA_DATA String
4 DEVICE_MAC String
13 APP_DATA String
当
NfcTagActionRecord
的Action=ACTION_IOT_ENV
数值 名称 数据类型 1 USER_ID String
2 OWNER_UID String
3 REGION String
4 SCENE_NAME String
其余情况参照NDEF类型为
com.xiaomi.mi_connect_service:externaltype
时的情况
DeviceType 设备类型
值范围:[0, 8)
数值 | 名称 |
---|---|
1 | DEVICE_TYPE_IOT |
2 | DEVICE_TYPE_MI_ROUTER |
3 | DEVICE_TYPE_MI_SOUND_BOX |
4 | DEVICE_TYPE_MI_LAPTOP |
5 | DEVICE_TYPE_MI_TV |
6 | DEVICE_TYPE_MI_PHONE |
7 | DEVICE_TYPE_IOT_USER_ENV |
默认 | “Unsupported device” |
NfcTagActionRecord
Record类型为:0x02
位置 | 含义 |
---|---|
0 | Record类型 |
1 ~ 2 | Record长度,16位整数 Short,整个长度而不只是后续数据长度 |
3 ~ 4 | action,16位整数 Short |
5 | condition,byte |
6 | 设备编号,无特殊定义 |
7 | flag(目前没用到) |
之后全部 | conditionParameters(目前没用到) |
Action
值范围:[0, 14)
数值 | 名称 |
---|---|
1 | ACTION_IOT |
2 | ACTION_MUSIC_RELAY |
3 | ACTION_TEL_RELAY |
4 | ACTION_FILE_TRANSFER |
5 | ACTION_SCREEN_CASTING |
6 | ACTION_CORP_OPERATION |
7 | ACTION_VIDEO_RELAY |
8 | ACTION_VOIP_RELAY |
9 | ACTION_IOT_ENV |
10 | ACTION_REMOTE_CONTROLLER |
11 | ACTION_GUEST_NETWORK |
12 | ACTION_EMPTY |
13 | ACTION_CUSTOM |
32767 | ACTION_AUTO (Short.MAX_VALUE) |
默认 | “Unsupported” |
注:ACTION_CUSTOM (13) 在代码中并无说明,只是为了方便引用描述自主加上的,目前只在AttrAdvData
的flags=0x01
时使用。
Condition
值范围:[0, 3)
数值 | 名称 |
---|---|
1 | CONDITION_APP_FOREGROUND |
2 | CONDITION_SCREEN_LOCKED |
127 | CONDITION_AUTO (Byte.MAX_VALUE) |
NfcExecutorFactory
NFC事件分发
事件分发以Action为区分,如以下未列出则当NDEF类型为com.xiaomi.mi_connect_service:externaltype
时无事件分发。
当NDEF类型为com.xiaomi.smarthome:externaltype
时有另外的分发方案,此处不做讨论。
AppDiscTypeEnum
名称 | 优先级 | ID | 是否广泛支持 |
---|---|---|---|
NONE | -1 | 0 | false |
BT | 1 | 1 | true |
BLE | 1 | 64 | true |
BT_CLASSIC | 1 | 128 | true |
IP_BONJOUR | 2 | 2 | true |
IP_P2P | 3 | 16 | true |
IP_SOFTAP | 4 | 32 | true |
NFC | 5 | 4 | false |
ACTION_SCREEN_CASTING
发送广播
Intent
- Action:
com.xiaomi.mi_connect_service.mi_play_endpoint_found
- Permission:
com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT
- Flags:
16777216
安卓FlagIntent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
32
安卓FlagIntent.FLAG_INCLUDE_STOPPED_PACKAGES
268435456
小米自定义FlagStaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
- Extra:
mac
DEVICE_ATTR_WIFI_MAC_ADDRESS (Mac的16进制字符串,用:
连接)disctype
4 AppDiscTypeEnum.NFC的整数IDrssi
整数 0name
字符串资源R.string.xiaomi_tv
英文和中文下为:“Xiaomi TV” / “小米电视”idhash
若action
=com.milink.service.CMD
,则new String(DEVICE_ATTR_ID_HASH)
,否则直接使用DEVICE_ATTR_ID_HASHcmd
整数 1wired_mac
DEVICE_ATTR_NIC_MAC_ADDRESS (Mac的16进制字符串,用:
连接)bt_mac
DEVICE_ATTR_BLUETOOTH_MAC_ADDRESS (Mac的16进制字符串,用:
连接)
ACTION_VOIP_RELAY
发送广播
Intent
- Action:
com.xiaomi.mi_connect_service.nfc_voip_relay_endpoint_found
- Permission:
com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT
- Flags:
16777216
安卓FlagIntent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
32
安卓FlagIntent.FLAG_INCLUDE_STOPPED_PACKAGES
268435456
小米自定义FlagStaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
- Extra:
mac
DEVICE_ATTR_WIFI_MAC_ADDRESS (Mac的16进制字符串,用:
连接)idhash
DEVICE_ATTR_ID_HASHbyte[]
disctype
4 AppDiscTypeEnum.NFC的整数IDname
字符串资源R.string.xiaomi_nfc_tag_device
英文和中文下为:“Mi Device” / “小米碰碰贴的设备”
ACTION_AUTO
若DEVICE_ATTR_MODEL为空,则默认为"screen"。
若设备类型为DEVICE_TYPE_MI_TV (5),则执行ACTION_SCREEN_CASTING
相同的分发。
若设备类型为DEVICE_TYPE_MI_SOUND_BOX (3),则:
- 若前台App为
com.xiaomi.mitime
(小米通话),则执行ACTION_SCREEN_CASTING
相同的分发。 - 否则执行
ACTION_MUSIC_RELAY
或ACTION_TEL_RELAY
相同的分发。
其它设备类型,则执行ACTION_MUSIC_RELAY
或ACTION_TEL_RELAY
相同的分发。
ACTION_GUEST_NETWORK
需要 DEVICE_ATTR_SSID (byte[]
转 UTF-8 字符串)
需要 DEVICE_ATTR_PASSWORD (byte[]
转 UTF-8 字符串)
这两个数据用于连接WIFI
ACTION_EMPTY
如果未安装com.xiaomi.smarthome
(米家):
Intent
- Action:
android.intent.action.VIEW
- Data:
market://details?id=com.xiaomi.smarthome
- Flags: BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE = 268435456
启动应用市场Activity安装com.xiaomi.smarthome
(米家)
如果已安装则不处理,让com.xiaomi.smarthome
(米家)去处理
ACTION_MUSIC_RELAY
或ACTION_TEL_RELAY
需要 DEVICE_ATTR_MODEL (byte[]
转 UTF-8 字符串)
需要 DEVICE_ATTR_BLUETOOTH_MAC_ADDRESS
连接到蓝牙并播放媒体(A2DP)
flags=0x01
V2版本协议
目前样例中的AttrAdvData
版本为:
- versionMajor: 1
- versionMinor: 11
这个版本AttrAdvData
的idHash
=0x00
。
appsData
的解析过程详见前文的NfcTagAppData
,格式是一样的。
发送广播
Intent
- Action:
com.xiaomi.nfc.action.TAG_DISCOVERED
- Permission:
com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT
Manifest中需要声明这个权限才能收听到广播 - Flags:
16777216
安卓FlagIntent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
32
安卓FlagIntent.FLAG_INCLUDE_STOPPED_PACKAGES
268435456
小米自定义FlagStaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
- Extra:
Action
整数 ACTION_CUSTOM 13IdHash
字符串Base64.encodeToString(DEVICE_ATTR_ID_HASH, Base64.DEFAULT)
WifiMac
字符串 DEVICE_ATTR_WIFI_MAC_ADDRESS (Mac的16进制字符串,用:
连接)BtAddress
字符串 DEVICE_ATTR_BLUETOOTH_MAC_ADDRESS (Mac的16进制字符串,用:
连接)
注:除Action
外,其他参数为空时会传递空字符串
flags=0x03
Handoff NFC协议
目前样例中的AttrAdvData
版本为:
- versionMajor: 1
- versionMinor: 13
这个版本未使用AttrAdvData
中的idHash
,所以不用填写。
appsData
appsData
中的每一个byte[]
由头部,数字键值对,尾部三个部分组成。
头部
位置 | 含义 |
---|---|
0 | 主版本号 |
1 | 次版本号 |
2 ~ 5 | 设备类型 |
主版本号和次版本号十进制为39和23,目前直接写死在代码里,只有相等时才能继续解析。
设备类型的种类详见后面的device_type_key
内容。
数字键值对
位置 | 含义 |
---|---|
6 | 数字键值对长度 |
键值对用以下格式反复
位置 | 含义 |
---|---|
0 | 键,整数 |
1 | 值长度,设为X |
2 ~ 2+X-1 |
值,byte[] |
尾部
假设数字键值对读取结束后的下一位为A
位置 | 含义 |
---|---|
A | 自定义ACTION广播的名称的长度,设为X |
A+1 ~ A+1+X-1 |
自定义ACTION广播的名称 |
之后全部 | 发送的byte[] 载荷 |
构建protocol_value_key
JSON
键 | 值 |
---|---|
device_type_key |
设备类型,将32字节的设备类型转为整数 |
attribute_value_key |
数字键值对数据转JSON后,UTF-8编码为Base64的字符串 |
protocol_payload_key |
发送的byte[] 载荷,转为的Base64的字符串 |
注:不存在某种数据就不存在这个JSON字段。
attribute_value_key
JSON:键数字转为字符串,目前byte[]
值直接交给org.json.JSONObject#put
处理(这样会出BUG,但是这个字段没被用到过)。
发送广播
目前自定义ACTION广播可用的[ACTION]
只有TAG_DISCOVERED
adb shell "dumpsys package resolvers receiver | grep com.xiaomi.nfc.action."
Intent
- Action:
com.xiaomi.nfc.action.[ACTION]
- Permission:
com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT
Manifest中需要声明这个权限才能收听到广播 - Flags:
16777216
安卓FlagIntent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
32
安卓FlagIntent.FLAG_INCLUDE_STOPPED_PACKAGES
268435456
小米自定义FlagStaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
- Extra:
protocol_value_key
内容为JSON字符串
对于以上Intent,使用ADB命令在手机上搜索可以得到com.milink.service/com.miui.circulate.nfc.relay.NfcTransportReceiver
:
adb shell cmd package query-receivers --components -a "com.xiaomi.nfc.action.TAG_DISCOVERED"
找到 com.milink.service
包名的App,名称为投屏
或者互联互通服务
。
接着就可以继续解析JSON字符串内的内容了。
device_type_key
设备类型定义
数值 | 名称 |
---|---|
2 | TV |
3 | PC |
5 | CAR |
8 | PAD |
protocol_payload_key
载荷
键值对用以下格式反复
位置 | 含义 |
---|---|
0 | 键,整数 |
1 | 值长度,设为X |
2 - 2+X-1 |
值,byte[] |
键值对
数值 | 名称 | 类型 | 解释 |
---|---|---|---|
101 | actionSuffix | UTF-8 String | Action后缀 |
1 | btMac | UTF-8 String | 蓝牙Mac地址 |
2 | wifiMac | UTF-8 String | WIFI Mac地址 |
3 | wiredMac | UTF-8 String | 有线Mac地址 |
121 | extAbility | bytes | 额外能力 |
actionSuffix
目前已知的actionSuffix
为:MIRROR
和TVCAST
adb shell "dumpsys package resolvers receiver | grep com.miui.onehop.action."
extAbility
目前只有一种:_ability_lyra
Boolean,要求(extAbility[0] & 1) > 0
发送自定义Action的广播
发送的是有序广播:OrderedBroadcast
Intent
- Action:
com.miui.onehop.action.[ACTION]
[ACTION]
=actionSuffix
- Permission:
com.miui.onehop.permission.MIRROR
- Flags: 添加了多组flags
32
安卓FlagIntent.FLAG_INCLUDE_STOPPED_PACKAGES
16777216
小米自定义FlagRemoteCallAdapterKt.FLAG_RECEIVER_INCLUDE_BACKGROUND
- Extra:
_device_type
int,设备类型,device_type_key
_bt_mac
String,蓝牙Mac地址,btMac
_wifi_mac
String,WIFI Mac地址,wifiMac
_wired_mac
String,有线Mac地址,wiredMac
_ability_lyra
Boolean,(extAbility[0] & 1) > 0
这个广播目前只由Appcom.milink.service
自己接收并处理。
样例解析
一碰妙享
NDEF 完整数据
d42a4d636f6d2e7869616f6d692e6d695f636f6e6e6563745f736572766963653a65787465726e616c747970650a4b0801100d2201032a094d492d4e4643544147380f4a31271700000003000e5441475f444953434f564552454465064d4952524f52011130303a30303a30303a30303a30303a30306a02fa7f
NDEF解析
Record 0
flags:
message_begin: True
message_end: True
chunked: False
short: True
id: False
tnf: 4 [EXTERNAL]
type_len: 42
type: b'com.xiaomi.mi_connect_service:externaltype'
id_len: 0
id: None
payload_len: 77
payload: 0a4b ... fa7f
payload解析
{
"advData": {
"appIds": [
16378
],
"appsData": [], // 数据过多不展示
"deviceType": 15,
"flags": "Aw==", // 0x03
"name": "MI-NFCTAG",
"versionMajor": 1,
"versionMinor": 13
}
}
数据解析
27:17:00:00:00:03:00:0e:54:41:47:5f:44:49:53:43:4f:56:45:52:45:44:65:06:4d:49:52:52:4f:52:01:11:30:30:3a:30:30:3a:30:30:3a:30:30:3a:30:30:3a:30:30
数据 | 名称 | 解释 |
---|---|---|
27:17 |
版本号 | 十进制为39 23 |
00:00:00:03 |
设备类型 | PC 3 |
00 |
数字键值对数据长度 | 无数据 |
0e |
自定义ACTION长度 | 十进制为14 |
54: ... :44 |
自定义ACTION名称 | TAG_DISCOVERED |
65 |
键 | actionSuffix 101 |
06 |
值长度 | 十进制为6 |
4d: ... :52 |
值 | MIRROR |
01 |
键 | btMac 1 |
11 |
值长度 | 十进制为17 |
30: ... :30 |
值,UTF-8字符串 | 00:00:00:00:00:00 |
小米音箱妙播
NDEF 完整数据
d42a4a636f6d2e7869616f6d692e6d695f636f6e6e6563745f736572766963653a65787465726e616c747970650a480801100b2201012a094d492d4e4643544147320100380f4a2b010063034f6b000201001b000300000001000611111111111000020006111111111111020008000d7f00006a02fa7f
NDEF解析
Record 0
flags:
message_begin: True
message_end: True
chunked: False
short: True
id: False
tnf: 4 [EXTERNAL]
type_len: 42
type: b'com.xiaomi.mi_connect_service:externaltype'
id_len: 0
id: None
payload_len: 74
payload: 0a48 ... fa7f
payload解析
{
"advData": {
"appIds": [
16378
],
"appsData": [], // 数据过多不展示
"deviceType": 15,
"flags": "AQ==", // 0x01
"idHash": "AA==", // 0x00
"name": "MI-NFCTAG",
"versionMajor": 1,
"versionMinor": 11
}
}
数据解析
01:00:63:03:4f:6b:00:02:01:00:1b:00:03:00:00:00:01:00:06:11:11:11:11:11:10:00:02:00:06:11:11:11:11:11:11:02:00:08:00:0d:7f:00:00
数据 | 名称 | 解释 |
---|---|---|
01:00 |
主和次版本号 | 十进制1和0 |
63:03:4f:6b |
写入时间 | 1661161323 = 2022-08-22 17:42:03 |
00 |
flag | |
02 |
Record数量 | 十进制2 |
01 |
Record类型 | NfcTagDeviceRecord |
00:1b |
Record长度 | 十进制27 |
00:03 |
设备类型 | DEVICE_TYPE_MI_SOUND_BOX 3 |
00 |
flag | |
00 |
设备编号 | 十进制0 |
00:01 |
属性类型 | DEVICE_ATTR_WIFI_MAC_ADDRESS 1 |
00:06 |
属性数据长度 | 十进制6 |
11:11:11:11:11:10 |
属性数据 | Mac地址byte[] |
00:02 |
属性类型 | DEVICE_ATTR_BLUETOOTH_MAC_ADDRESS 2 |
00:06 |
属性数据长度 | 十进制6 |
11:11:11:11:11:11 |
属性数据 | Mac地址byte[] |
02 |
Record类型 | NfcTagActionRecord |
00:08 |
Record长度 | 十进制8 |
00:0d |
Action | ACTION_CUSTOM 13 |
7f |
Condition | CONDITION_AUTO 127 |
00 |
设备编号 | 十进制0 |
00 |
flag |
小米蓝牙音箱
NDEF 完整数据
942a65636f6d2e7869616f6d692e6d695f636f6e6e6563745f736572766963653a65787465726e616c747970650a63080110022201002a094d492d4e4643544147320100380f4a460100646e0c840002010036000300000001000600000000000000020006000000000000001200177869616f6d692e77696669737065616b65722e783038630200087fff7f00006a02fa7f51010e5504672e686f6d652e6d692e636f6d
NDEF解析
Record 0
flags:
message_begin: True
message_end: False
chunked: False
short: True
id: False
tnf: 4 [EXTERNAL]
type_len: 42
type: b'com.xiaomi.mi_connect_service:externaltype'
id_len: 0
id: None
payload_len: 101
payload: 0a63 ... fa7f
Record 1
flags:
message_begin: False
message_end: True
chunked: False
short: True
id: False
tnf: 1 [WELL_KNOWN]
type_len: 1
type: b'U' [URI]
id_len: 0
id: None
payload_len: 14
payload: 04672e686f6d652e6d692e636f6d
payload解析
{
"advData": {
"appIds": [
16378
],
"appsData": [], // 数据过多不展示
"deviceType": 15,
"flags": "AA==", // 0x00
"idHash": "AA==", // 0x00
"name": "MI-NFCTAG",
"versionMajor": 1,
"versionMinor": 2
}
}
数据解析
01:00:64:6e:0c:84:00:02:01:00:36:00:03:00:00:00:01:00:06:00:00:00:00:00:00:00:02:00:06:00:00:00:00:00:00:00:12:00:17:78:69:61:6f:6d:69:2e:77:69:66:69:73:70:65:61:6b:65:72:2e:78:30:38:63:02:00:08:7f:ff:7f:00:00
数据 | 名称 | 解释 |
---|---|---|
01:00 |
主和次版本号 | 十进制1和0 |
64:6e:0c:84 |
写入时间 | 1684933764 = 2023-05-24 21:09:24 |
00 |
flag | |
02 |
Record数量 | 十进制2 |
01 |
Record类型 | NfcTagDeviceRecord |
00:36 |
Record长度 | 十进制54 |
00:03 |
设备类型 | DEVICE_TYPE_MI_SOUND_BOX 3 |
00 |
flag | |
00 |
设备编号 | 十进制0 |
00:01 |
属性类型 | DEVICE_ATTR_WIFI_MAC_ADDRESS 1 |
00:06 |
属性数据长度 | 十进制6 |
00:00:00:00:00:00 |
属性数据 | Mac地址byte[] |
00:02 |
属性类型 | DEVICE_ATTR_BLUETOOTH_MAC_ADDRESS 2 |
00:06 |
属性数据长度 | 十进制6 |
00:00:00:00:00:00 |
属性数据 | Mac地址byte[] |
00:02 |
属性类型 | DEVICE_ATTR_MODEL 18 |
00:17 |
属性数据长度 | 十进制23 |
78: ... :63 |
属性数据 | xiaomi.wifispeaker.x08c |
02 |
Record类型 | NfcTagActionRecord |
00:08 |
Record长度 | 十进制8 |
7f:ff |
Action | ACTION_AUTO 32767 |
7f |
Condition | CONDITION_AUTO 127 |
00 |
设备编号 | 十进制0 |
00 |
flag |
触发小米一碰妙享的其他方法
如果想要代码触发小米一碰妙享功能有两种方法:
- 发送虚拟的NFC标签的NDEF信息
- 直接向
投屏
或者互联互通服务
发送广播
发送虚拟的NFC标签的NDEF信息
安卓中NDEF广播要求必须有android.nfc.extra.TAG
的Tag
数据,但是这个数据只有真实的NFC硬件才能生成。
因此一个安全的解决方案是,对于已有但不能修改的NFC标签,可以使用代码获取到这个NFC标签的硬件Tag
数据,并对其中的内容修改后转发。
// 从Intent中获取已有的NDEF数据
val tag: Tag? = IntentCompat.getParcelableExtra(intent, NfcAdapter.EXTRA_TAG, Tag::class.java)
val id: ByteArray = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID)
val btMac = "00:00:00:00:00:00"
val hexPart1 = "0a4b0801100d2201032a094d492d4e4643544147380f4a3127170000000"
val hexPart2 = "000e5441475f444953434f564552454465064d4952524f520111"
val hexPart3 = "6a02fa7f"
// PC
val hex = hexPart1 + "3" + hexPart2 + btMac.toByteArray().toHexString() + hexPart3
val payload = hex.decodeHex()
val externalType = "com.xiaomi.mi_connect_service:externaltype"
val record = NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, externalType.toByteArray(), null, payload)
Intent(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
data = Uri.parse("vnd.android.nfc://ext/$externalType")
putExtra(NfcAdapter.EXTRA_TAG, tag)
putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, arrayOf(NdefMessage(record)))
putExtra(NfcAdapter.EXTRA_ID, id)
}.also {
ContextCompat.startActivity(this, it, null)
}
另一方面,com.xiaomi.mi_connect_service
(小米互联通信服务
)在处理收到的数据时,只有flags != 1
且flags != 3
时才会获取并处理Tag
。
因此想要模拟这个广播,另一个不安全但是能用的方案就是不传递这个Tag
数据,这样在当前使用场景下不会报错。
// 直接设置为空即可
val tag: Tag? = null
// 一个空的NFC Tag ID
val id: ByteArray = "000000000000".decodeHex()
// 类似的代码 ...
关于如何接收指定的NDEF数据,可以查看官方文档。
向投屏
或者互联互通服务
发送广播
模拟NDEF数据总可能会出现一些问题,因此另一个方案就是直接发送投屏的广播。
根据广播权限的定义,应用程序需要拥有com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT
权限,才能向同样要求这个权限的广播接收器发送广播。
值得庆幸的是,这个权限定义在com.xiaomi.mi_connect_service
(小米互联通信服务
)中并且没有任何的签名或者系统限制,所以可以直接在测试APP的Manifest中申明这个权限。
val btMac = "00:00:00:00:00:00"
val hex = "65064d4952524f520111" + btMac.toByteArray().toHexString()
val payload = Base64.encodeToString(hex.decodeHex(), Base64.DEFAULT)
val json = JSONObject()
json.put("device_type_key", 3) // PC
json.put("protocol_payload_key", payload)
val extra = json.toString()
sendBroadcast(Intent("com.xiaomi.nfc.action.TAG_DISCOVERED").apply {
addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
addFlags(268435456)
putExtra("protocol_value_key", extra)
}, "com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT")
一碰妙享传文件
触发一碰妙享传文件的要求是文件/相册APP在前台,并且选中了要传输的文件。
如果想要触发,那么发送Intent的APP就必须在后台,或者不显示任何界面的发送完广播就退出。
Activity不显示界面可以使用以下Theme:
<activity android:theme="@android:style/Theme.NoDisplay" />
一碰妙享lyra连接
目前实测启用_ability_lyra
能够提高设备发现与连接的速度,因此建议增加这个选项的数据。
在字节数组得到蓝牙地址的后面加入79:01:01
的字节数组即可。