BT_ uuid & profile & service映射关系描述

基于Android P版本分析

BT UUID

我们针对device蓝牙连接过程中的一些流程进行分析,我们不针对常见的开启、扫描、绑定这3个操作进行分析,主要围绕这3个过程中所执行的一些逻辑,例如创建了哪些实例,实例的作用是什么等一些信息进行表述;

CachedBluetoothDevice

CachedBluetoothDevice 创建

我们知道,在BT扫描阶段发现了可见蓝牙设备之后,就会发送ACTION_FOUND广播来通知应用层,一般为Settings进程,Settings进程在接收到广播之后,就会触发SettingsLib中的BluetoothEventManager中的广播接收器,然后将消息发送给DeviceFoundHandler;

在接收到ACTION_FOUND广播中,同时会将扫描到的BluetoothDevice对象进行回调上报,在DeviceFoundHandler中会将BluetoothDevice封装成CachedBluetoothDevice实例;

CachedBluetoothDevice 详解

ini 复制代码
CachedBluetoothDevice(Context context,
                      LocalBluetoothAdapter adapter,
                      LocalBluetoothProfileManager profileManager,
                      BluetoothDevice device) {
    mContext = context;
    mLocalAdapter = adapter;
    mProfileManager = profileManager;
    mAudioManager = context.getSystemService(AudioManager.class);
    mDevice = device;
    mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
    fillData();
    mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID;
}

CachedBluetoothDevice是BluetoothDevice在framework层中的一个缓存管理,用于描述BluetoothDevice的一些信息状态和操作方法;

在CachedBluetoothDevice构造方法中,持有了LocalBluetoothAdapter、LocalBluetoothProfileManager和AudioManager的实例引用;在该类中,大多数属性和方法都和BluetoothDevice实例有很大的关系;

mProfiles 服务集合

其中在该类中有一个比较主要的变量:mProfiles;

这个集合主要是用于维护BluetoothDevice中支持的服务对应的配置信息,每一个服务对应一个Profile,在framework中体现为LocalBluetoothProfile;

在连接device的时候,即对应CachedBluetoothDevice时,需要将CachedBluetoothDevice中维护的device对应的服务集合遍历进行启动并开启对应的状态机进行状态维护;

那这样就带来一个问题,Device支持的服务是如何定义的,手机端连接车机蓝牙时,如何得知车机具备哪些服务;

BT Profile_Service

首先,我们先看一下CachedBluetoothDevice中Profile集合的填充流程,通过流程,我们分析其中涉及到的一些概念;

Profile 加载

我们以车机设备为例,在开启蓝牙设备的时候,主要是进行AdapterService的初始化、连接操作以及其他各个类型的ProfileService的启动;

在Service启动之前,首先Bluedroid会上报蓝牙设备的UUID,确认该设备支持的Server集合;

然后紧接着才是各个Service的启动过程;我们以其中的一个Service为例,说明Service的启动、加载流程;

在A2dpSinkService连接过程中,在最后的逻辑中,A2dpSinkStateMachine的状态机进入了Pending状态,等待Bluedroid层的回调结果;

到了这一步,说明A2dpSinkService已经连接成功,状态机已经处于了Connected状态了。

当所有的Service(支持的ProfileService)都启动成功之后,Bluetooth状态机会进入BluetoothAdapter.OnState状态,之后就会进行Profile的加载保存,执行setBluetoothStateOn方法;

UUID

上述我们提及到了UUID的概念,UUID:Universally Unique Identifier,通用唯一识别码,由三部分组成,当前的日期和时间、时钟序列、全球唯一的IEEE设备识别号,UUID是根据一定算法,计算得到的一长串数字,这个数字的产生使用了多种元素,所以使得这串数字不会重复,每次生成都会产生不一样的序列,所以可以用来作为唯一标识;

对于蓝牙设备,每一个服务都有一个与之对应的UUID,在frameworks层中的定义在BluetoothUuid.java中,下面是我们常使用到的一些server对应的uuid的映射关系表;

Type UUID
AudioSink 0000110B
AudioSource 0000110A
AdvAudioDist 0000110D
HSP 00001108
HSP_AG 00001112
Handsfree 0000111E
Handsfree_AG 0000111F
AvrcpController 0000110E
AvrcpTarget 0000110C
ObexObjectPush 00001105
Hid 00001124
Hogp 00001812
PANU 00001115
NAP 00001116
BNEP 0000000f
PBAP_PCE 0000112e
PBAP_PSE 0000112f
MAP 00001134
MNS 00001133
MAS 00001132
SAP 0000112D
HearingAid 0000FDF0
DIP 00001200
BASE_UUID 00000000

蓝牙技术联盟SIG定义了UUID共用了一个基本的UUID:0000xxxx-0000-1000-8000-00805F9B34FB,共128位,为了进一步简化基本UUID,蓝牙技术联盟定义了一个16位的UUID,便于记忆和操作,其中xxxx部分不固定的,上述UUID后4位数字对应了该部分的值,也就是简化后的UUID;

RemoteDevice UUID

我们这边以RemoteDevice侧的UUID来分析一下,如何通过UUID来确定该RemoteDevice支持哪些Service;

测试设备:华为P40

yaml 复制代码
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice: Class: 5a020c
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice: UUID:
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   0000110a-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   00001105-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   00001115-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   00001116-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   0000112f-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   00001112-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   0000111f-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   00001132-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   00000000-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   00000000-0000-1000-8000-00805f9b34fb
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   11c8b310-80e4-4276-afc0-f81590b2177f
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   8ce255c0-200a-11e0-ac64-0800200c9a66
05-09 15:30:41.802  4764  4764 V CachedBluetoothDevice:   9664aa26-d76c-43ad-9775-d310f253a408
05-09 15:30:41.802  1297  1297 E CachedBluetoothDevice: updating profiles for dupz
  • class:BluetoothClass,指定了BluetoothDevice;
  • UUID:BluetoothDevice的唯一标识;
  • dupz:BluetoothDevice name;

上述的3个参数是关于Device基本信息或概念的描述;

UUID Server 映射关系
  • 0000110a -0000-1000-8000-00805f9b34fb:AudioSource
  • 00001105 -0000-1000-8000-00805f9b34fb:ObexObjectPush
  • 00001115 -0000-1000-8000-00805f9b34fb:PANU
  • 00001116 -0000-1000-8000-00805f9b34fb:NAP
  • 0000112f -0000-1000-8000-00805f9b34fb:PBAP_PSE(Phonebook Access Server)
  • 00001112 -0000-1000-8000-00805f9b34fb:HSP_AG(Headset AG)
  • 0000111f -0000-1000-8000-00805f9b34fb:Handsfree_AG(Handsfree Audio Gateway)
  • 00001132 -0000-1000-8000-00805f9b34fb:MAS(Message Access Server)
  • 00000000 -0000-1000-8000-00805f9b34fb:BASE_UUID
  • 11c8b310-80e4-4276-afc0-f81590b2177f:Vendor specific
  • 8ce255c0-200a-11e0-ac64-0800200c9a66:BluetoothChat,google提供的一个特定的UUID,属于Vendor specific
  • 9664aa26-d76c-43ad-9775-d310f253a408:Vendor specific
UUID Profile 映射关系

这个映射关系定义在LocalBluetoothProfileManager的updateProfiles方法中;

在这个方法中涉及到了两组UUID:RemoteDevice UUID和LocalDevice UUID,即手机和车机的UUID;

因为有一些服务连接中,存在C/S模式,那就需要保证两侧满足该模式,只要有一侧不支持或者两侧都不支持,那在连接过程中,该服务就不需要启动运行;

  • 绿色:BluetoothUuid常量值;
  • 蓝色:BluetoothUuid的父节点,只有在该父节点下的所有的子节点(BluetoothUuid)都满足(两侧)的情况下,才可以加载指定的Profile;
  • 红色:代表Profile与BluetoothUuid无关;

上述描述了Profile和UUid的映射关系;

LocalBluetoothProfile & BluetoothProfile 映射关系

LocalBluetoothProfile是一个接口,各种协议的封装者实现了该接口;

BluetoothProfile表示蓝牙配置文件的接口。蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。

LocalBluetoothProfile的实现依托于BluetoothProfile实现的;

LocalBluetoothProfile BluetoothProfile(Service) UUIDs
OppProfile - -
PbapServerProfile BluetoothPbap BluetoothUuid.HSP BluetoothUuid.Handsfree BluetoothUuid.PBAP_PCE
PanProfile BluetoothPan -
HidDeviceProfile BluetoothHidDevice -
HidProfile BluetoothHidHost -
A2dpSinkProfile BluetoothA2dpSink BluetoothUuid.AudioSource BluetoothUuid.AdvAudioDist
MapProfile BluetoothMap BluetoothUuid.MAP BluetoothUuid.MNS BluetoothUuid.MAS
HfpClientProfile BluetoothHeadsetClient BluetoothUuid.HSP_AG BluetoothUuid.Handsfree_AG
MapClientProfile BluetoothMapClient BluetoothUuid.MAP BluetoothUuid.MNS BluetoothUuid.MAS
PbapClientProfile BluetoothPbapClient BluetoothUuid.PBAP_PSE
SapProfile BluetoothSap BluetoothUuid.SAP

Service 集合确认

蓝牙模块定义了很多ProfileService,例如A2dpService、A2dpSinkService、HfpService等等;

但是蓝牙设备并不是需要支持全部的Service,例如蓝牙音响,只需要支持播放音频就可以,但是手机则需要支持文件传输功能、远程通话功能,这些Service的确定,依赖于UUID;

我们首先回顾之前的逻辑,在开启蓝牙之后,我们就会调用setAllProfileServiceStates方法去加载所有的service,这里的逻辑就是用于加载所有该蓝牙设备支持的service集合;

核心逻辑:getSupportedProfiles,这个方法返回了车机设备Bluetooth模块定义的支持的Service的集合;

Service profile google_value overlay_value value
A2dpService profile_supported_a2dp false false false
A2dpSinkService profile_supported_a2dp_sink true true true
HealthService profile_supported_hdp false false false
HeadsetService profile_supported_hs_hfp false false false
HeadsetClientService profile_supported_hfpclient true true true
HidHostService profile_supported_hid_host true true true
BluetoothOppService profile_supported_opp false true true
PanService profile_supported_pan false false false
BluetoothPbapService profile_supported_pbap false false false
GattService profile_supported_gatt true true
pbap_include_photos_in_vcard true true
pbap_use_profile_for_owner_vcard true true
BluetoothMapService profile_supported_map false false false
AvrcpTargetService profile_supported_avrcp_target false false
AvrcpControllerService profile_supported_avrcp_controller true true true
SapService profile_supported_sap false false
PbapClientService profile_supported_pbapclient true true true
MapClientService profile_supported_mapmce false false false
HidDeviceService profile_supported_hid_device false false
HearingAidService profile_supported_hearing_aid false false
enable_phone_policy false false
hfp_client_connection_service_enabled true true

上述的profile这些定义,都是在Bluetooth模块的AndroidManifest.xml中使用;

ini 复制代码
<service
    android:process="@string/process"
    android:name=".mapclient.MapClientService"
    android:enabled="@bool/profile_supported_mapmce" >
    <intent-filter>
        <action android:name="android.bluetooth.IBluetoothMapClient" />
    </intent-filter>
</service>

例如MapClientService的标签中,属性enabled使用到了上述的定义,判断这个service是否能被系统实例化,如果能则为true,否则为false,默认为true;

同时也会在Config中使用,用于判断哪些Service是Bluetooth支持的;

scss 复制代码
private static final ProfileConfig[] PROFILE_SERVICES_AND_FLAGS = {
    new ProfileConfig(HeadsetService.class, R.bool.profile_supported_hs_hfp,
                      (1 << BluetoothProfile.HEADSET)),
    new ProfileConfig(A2dpService.class, R.bool.profile_supported_a2dp,
                      (1 << BluetoothProfile.A2DP)),
    new ProfileConfig(A2dpSinkService.class, R.bool.profile_supported_a2dp_sink,
                      (1 << BluetoothProfile.A2DP_SINK)),
    new ProfileConfig(HidHostService.class, R.bool.profile_supported_hid_host,
                      (1 << BluetoothProfile.HID_HOST)),
    new ProfileConfig(HealthService.class, R.bool.profile_supported_hdp,
                      (1 << BluetoothProfile.HEALTH)),
    new ProfileConfig(PanService.class, R.bool.profile_supported_pan,
                      (1 << BluetoothProfile.PAN)),
    new ProfileConfig(GattService.class, R.bool.profile_supported_gatt,
                      (1 << BluetoothProfile.GATT)),
    new ProfileConfig(BluetoothMapService.class, R.bool.profile_supported_map,
                      (1 << BluetoothProfile.MAP)),
    new ProfileConfig(HeadsetClientService.class, R.bool.profile_supported_hfpclient,
                      (1 << BluetoothProfile.HEADSET_CLIENT)),
    new ProfileConfig(AvrcpTargetService.class, R.bool.profile_supported_avrcp_target,
                      (1 << BluetoothProfile.AVRCP)),
    new ProfileConfig(AvrcpControllerService.class,
                      R.bool.profile_supported_avrcp_controller,
                      (1 << BluetoothProfile.AVRCP_CONTROLLER)),
    new ProfileConfig(SapService.class, R.bool.profile_supported_sap,
                      (1 << BluetoothProfile.SAP)),
    new ProfileConfig(PbapClientService.class, R.bool.profile_supported_pbapclient,
                      (1 << BluetoothProfile.PBAP_CLIENT)),
    new ProfileConfig(MapClientService.class, R.bool.profile_supported_mapmce,
                      (1 << BluetoothProfile.MAP_CLIENT)),
    new ProfileConfig(HidDeviceService.class, R.bool.profile_supported_hid_device,
                      (1 << BluetoothProfile.HID_DEVICE)),
    new ProfileConfig(BluetoothOppService.class, R.bool.profile_supported_opp,
                      (1 << BluetoothProfile.OPP)),
    new ProfileConfig(BluetoothPbapService.class, R.bool.profile_supported_pbap,
                      (1 << BluetoothProfile.PBAP)),
    new ProfileConfig(HearingAidService.class, R.bool.profile_supported_hearing_aid,
                      (1 << BluetoothProfile.HEARING_AID))
};

判断条件:

ini 复制代码
static void init(Context ctx) {
    if (ctx == null) {
        return;
    }
    Resources resources = ctx.getResources();
    if (resources == null) {
        return;
    }
​
    ArrayList<Class> profiles = new ArrayList<>(PROFILE_SERVICES_AND_FLAGS.length);
    for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
        boolean supported = resources.getBoolean(config.mSupported);
        if (supported && !isProfileDisabled(ctx, config.mMask)) {
            Log.v(TAG, "Adding " + config.mClass.getSimpleName());
            profiles.add(config.mClass);
        }
        sSupportedProfiles = profiles.toArray(new Class[profiles.size()]);
    }
​
    mIsUserBuild = "user".equalsIgnoreCase(SystemProperties.get("ro.build.type","user"));
}
​
private static boolean isProfileDisabled(Context context, long profileMask) {
    final ContentResolver resolver = context.getContentResolver();
    final long disabledProfilesBitMask =
        Settings.Global.getLong(resolver, Settings.Global.BLUETOOTH_DISABLED_PROFILES, 0);
​
    return (disabledProfilesBitMask & profileMask) != 0;
}

其中根据profile的supported配置以及BLUETOOTH_DISABLED_PROFILES属性共同决定蓝牙设置是否支持该Service,最后通过startService的方法将sSupportedProfiles变量中保存的Service全部启动即可;

log分析

yaml 复制代码
05-09 15:30:18.648 17508 17508 V AdapterServiceConfig: Adding A2dpSinkService
05-09 15:30:18.648 17508 17508 V AdapterServiceConfig: Adding HidHostService
05-09 15:30:18.648 17508 17508 V AdapterServiceConfig: Adding GattService
05-09 15:30:18.648 17508 17508 V AdapterServiceConfig: Adding HeadsetClientService
05-09 15:30:18.649 17508 17508 V AdapterServiceConfig: Adding AvrcpControllerService
05-09 15:30:18.649 17508 17508 V AdapterServiceConfig: Adding SapService
05-09 15:30:18.649 17508 17508 V AdapterServiceConfig: Adding PbapClientService
05-09 15:30:18.649 17508 17508 V AdapterServiceConfig: Adding BluetoothOppService

基本上和上面红色字体表述的Service对应,SapService没有确认为什么会启动,后续再分析一下;

Profile 集合确认

需要启动的Service集合确认完成,这个代表了车机侧需要启动的Service,但是并不代表启动的Service都需要运行,因为蓝牙功能的正常运行需要C/S的配合,即需要判断RemoteDevice支持哪些协议,client和Server两侧的Profile交叉重叠的才是真正需要运行的协议或者是功能;

我们通过从手机上抓取log可知车机蓝牙UUID:

less 复制代码
05-11 19:12:06.400 31145 32227 I bt_btif_dm: btif_dm_search_services_evt index:0 uuid:0000111e-0000-1000-8000-00805f9b34fb
05-11 19:12:06.400 31145 32227 I bt_btif_dm: btif_dm_search_services_evt index:1 uuid:00001105-0000-1000-8000-00805f9b34fb
05-11 19:12:06.400 31145 32227 I bt_btif_dm: btif_dm_search_services_evt index:2 uuid:0000112d-0000-1000-8000-00805f9b34fb
05-11 19:12:06.400 31145 32227 I bt_btif_dm: btif_dm_search_services_evt index:3 uuid:0000110b-0000-1000-8000-00805f9b34fb
05-11 19:12:06.400 31145 32227 I bt_btif_dm: btif_dm_search_services_evt index:4 uuid:0000110e-0000-1000-8000-00805f9b34fb

车机蓝牙UUID

  • 0000111e:Handsfree
  • 00001105:ObexObjectPush
  • 0000112d:SAP
  • 0000110b:AdvAudioDist
  • 0000110e:AvrcpController

手机蓝牙UUID

  • 0000110a -0000-1000-8000-00805f9b34fb:AudioSource
  • 00001105 -0000-1000-8000-00805f9b34fb:ObexObjectPush
  • 00001115 -0000-1000-8000-00805f9b34fb:PANU
  • 00001116 -0000-1000-8000-00805f9b34fb:NAP
  • 0000112f -0000-1000-8000-00805f9b34fb:PBAP_PSE(Phonebook Access Server)
  • 00001112 -0000-1000-8000-00805f9b34fb:HSP_AG(Headset AG)
  • 0000111f -0000-1000-8000-00805f9b34fb:Handsfree_AG(Handsfree Audio Gateway)
  • 00001132 -0000-1000-8000-00805f9b34fb:MAS(Message Access Server)
  • 00000000 -0000-1000-8000-00805f9b34fb:BASE_UUID
  • 11c8b310-80e4-4276-afc0-f81590b2177f:Vendor specific
  • 8ce255c0-200a-11e0-ac64-0800200c9a66:BluetoothChat,google提供的一个特定的UUID,属于Vendor specific
  • 9664aa26-d76c-43ad-9775-d310f253a408:Vendor specific

我们知道,在之前描述UUID和Profile映射关系的时候,有些Profile的确定是需要两侧(手机侧和车机侧)共同持有匹配的UUID组合,Profile才能被加载;

我们根据上述的两侧的UUID,来确定一下最终需要被加载的Profile;

Phone UUID Car UUID Local Profile Profile
0000111e(Handsfree) 0000111f(Handsfree_AG) HfpClientProfile BluetoothHeadsetClient
00001105(ObexObjectPush) OppProfile -
0000112d(SAP) SapProfile BluetoothSap
0000110a(AudioSource) 0000110b(AdvAudioDist) A2dpSinkProfile BluetoothA2dpSink
0000110e(AvrcpController) BluetoothAvrcpController

从log中提示,车机侧的Profile集合为:

arduino 复制代码
New Profiles[HEADSET_CLIENT, A2DPSink, PAN, MAP Client, PbapClient]

这个是根据UUID匹配出来的Profile,但是实际加载的Profile却又是另一回事儿;

我们从图中可以看出,通过UUID匹配出来的是可支持的部分的Profiles,还不是真正的全部的Profile,因为有一部分Profile的加载不依赖BluetoothUUID,例如PbapClientProfile(电话簿),和真正开启的Profile是有本质区别的;

如上图,我们可以看出,提供可选择的开关:

  • 通话音频 -- HfpClientProfile&PbapClientProfile;
  • 媒体音频 -- AudioSink;
相关推荐
小马爱打代码4 小时前
微服务外联Feign调用:第三方API调用的负载均衡与容灾实战
微服务·架构·负载均衡
雨白8 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
9527华安8 小时前
FPGA实现40G网卡NIC,基于PCIE4C+40G/50G Ethernet subsystem架构,提供工程源码和技术支持
fpga开发·架构·网卡·ethernet·nic·40g·pcie4c
kk爱闹10 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空11 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭12 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日13 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安13 小时前
Android Library Maven 发布完整流程指南
android
guojl13 小时前
深度解决大文件上传难题
架构
岁月玲珑13 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio