致谢

首先感谢酷安各位小米设备拥有者的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在Manifestactivity中声明以下内容:

<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.NfcFieldOnPublishercom.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标签中AttrAdvDatadeviceType均为:15
所有NFC标签中AttrAdvDataname均为: “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标签中AttrAdvDataappId均为:MI_TAP (16378)

flags

数字 操作
0 或 Others 发送 com.xiaomi.aiot.nfc.message 本地广播,转发NDEF的TagRecord数据,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

这个版本AttrAdvDataidHash=0x00

发送内部广播

Intent

  • Action: com.xiaomi.aiot.nfc.message
  • Extra:
    • com.xiaomi.aiot.nfc.extra.adv NdefRecord.getPayload() 格式为AttrAdvDatabyte[]数据
    • 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目前有两种类型NfcTagDeviceRecordNfcTagActionRecord

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时:

    NfcTagActionRecordAction=ACTION_IOT

    数值 名称 数据类型
    1 DEVICE_ID String
    2 USER_MODEL String
    3 NFC_EXTRA_DATA String
    4 DEVICE_MAC String
    13 APP_DATA String

    NfcTagActionRecordAction=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) 在代码中并无说明,只是为了方便引用描述自主加上的,目前只在AttrAdvDataflags=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 安卓Flag Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
    • 32 安卓Flag Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    • 268435456 小米自定义Flag StaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
  • Extra:
    • mac DEVICE_ATTR_WIFI_MAC_ADDRESS (Mac的16进制字符串,用:连接)
    • disctype 4 AppDiscTypeEnum.NFC的整数ID
    • rssi 整数 0
    • name 字符串资源R.string.xiaomi_tv
      英文和中文下为:“Xiaomi TV” / “小米电视”
    • idhashaction=com.milink.service.CMD,则new String(DEVICE_ATTR_ID_HASH),否则直接使用DEVICE_ATTR_ID_HASH
    • cmd 整数 1
    • wired_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 安卓Flag Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
    • 32 安卓Flag Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    • 268435456 小米自定义Flag StaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
  • Extra:
    • mac DEVICE_ATTR_WIFI_MAC_ADDRESS (Mac的16进制字符串,用:连接)
    • idhash DEVICE_ATTR_ID_HASH byte[]
    • disctype 4 AppDiscTypeEnum.NFC的整数ID
    • name 字符串资源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_RELAYACTION_TEL_RELAY相同的分发。

其它设备类型,则执行ACTION_MUSIC_RELAYACTION_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_RELAYACTION_TEL_RELAY

需要 DEVICE_ATTR_MODEL (byte[] 转 UTF-8 字符串) 需要 DEVICE_ATTR_BLUETOOTH_MAC_ADDRESS

连接到蓝牙并播放媒体(A2DP)

flags=0x01 V2版本协议

目前样例中的AttrAdvData版本为:

  • versionMajor: 1
  • versionMinor: 11

这个版本AttrAdvDataidHash=0x00

appsData的解析过程详见前文的NfcTagAppData,格式是一样的。

发送广播

Intent

  • Action: com.xiaomi.nfc.action.TAG_DISCOVERED
  • Permission: com.xiaomi.mi_connect_service.permission.RECEIVE_ENDPOINT Manifest中需要声明这个权限才能收听到广播
  • Flags:
    • 16777216 安卓Flag Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
    • 32 安卓Flag Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    • 268435456 小米自定义Flag StaticConfigServiceConnector.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
  • Extra:
    • Action 整数 ACTION_CUSTOM 13
    • IdHash 字符串 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_keyJSON

device_type_key 设备类型,将32字节的设备类型转为整数
attribute_value_key 数字键值对数据转JSON后,UTF-8编码为Base64的字符串
protocol_payload_key 发送的byte[]载荷,转为的Base64的字符串

注:不存在某种数据就不存在这个JSON字段。
attribute_value_keyJSON:键数字转为字符串,目前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 安卓Flag Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
    • 32 安卓Flag Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    • 268435456 小米自定义Flag StaticConfigServiceConnector.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为:MIRRORTVCAST

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 安卓Flag Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    • 16777216 小米自定义Flag RemoteCallAdapterKt.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.TAGTag数据,但是这个数据只有真实的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 != 1flags != 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的字节数组即可。