Android蓝牙完全学习手册


1.前言

市面上关于Android的技术书籍很多,几乎每本书也都会涉及到蓝牙开发,但均是上层应用级别的,而且篇幅也普遍短小。对于手机行业的开发者,要进行蓝牙模块的维护,就必须从Android系统底层,至少框架层开始,了解蓝牙的结构和代码实现原理。这方面的文档、网上的各个论坛的相关资料却少之又少。分析原因,大概因为虽然蓝牙协议是完整的,但是并没有具体的实现。蓝牙芯片公司只负责提供最底层的API,与上层的适配和其他元件的兼容,需要各个厂家自己去实现,因此并未出现适用非常广泛的标准API供各个领域的公司使用。而实现了自己适配的公司,出于技术的保护又很少公开相关技术代码或者资料。

作为Android手机系统应用维护工程师,初学蓝牙模块也深感资料匮乏。阅读MTK的PPT,总是过分简略不够深入。阅读代码当然是好办法,但是没有指导,容易因理解不到位而出错和绕弯路,难免费时费力。基于这种现状,我将自己的蓝牙学习、代码分析总结出来,形成此文,一来梳理自身的蓝牙技术知识,而来贡献力量将本Team的知识积累建设得更加到位。希望后来者有文档可依,学习上手能更加便捷。由于作者水平有限,文字和理解的勘误难免,如果能互相指教提高,便是最大的荣幸了!

阅读本文后面详细分析,推荐的方法是打开一套工程源码,一边利用本文粘贴出来的代码和对应的说明文字,一边利用工程源码,对照阅读。这样遇到跳转的时候可以直接操作,不至于跟丢致使茫然无从。文字总无法一一俱到,遇到部分没有讲到的但或许对于特定读者却有疑惑的地方,请使用手边的源码认真分析。这样在作者看来学习提高是比较快的。

2.技术起源和名称来历

蓝牙(Bluetooth)是一种短距离的无线通信技术标准。它最初由瑞典的爱立信公司创制,技术始于公司的1994方案,后来由蓝牙技术联盟订定技术标准,1999年7月26日正式公布1.0版。蓝牙名字来源于10世纪丹麦国王Harald Blatand,英文名字是Harold Bluetooth。无线行业协会组织人员进行讨论,有人认为用Blatand国王的名字命名这种无线技术再好不过了,因为Blatand国王将挪威、瑞典和丹麦统一起来,就如同这项技术将统一无线通信领域一样。因此,蓝牙的名字就这样定了下来。它的标志由H和B两个字母合成,如下图:
蓝牙标志示意图
图1 蓝牙标志示意图

3.技术规格

蓝牙采用了分散式网络结构以及快跳频和短包技术,支持点对点及点对多点的通信。

3.1射频与基带部分

Bluetooth设备工作在全球通用的2.4GHz的ISM(Industrial,Science and Medicine)频段,在北美和欧洲为2400~2483.5MHz,使用79个频道,载频为2402+kMHz(k=0,1…,22)。无论是79个频道还是23个频道,频道间隔均为1MHz,采用时分双工(TDD,TimeDivision Duplex)方式。调制方式为BT=0.5的GFSK,调制指数为0.28~0.35,最大发射功率分为三个等级,分别是:100mW(20dBm),2.5mW(4dBm)和1mW(0dBm),在4~20dBm范围内要求采用功率控制,因此,Bluetooth设备间的有效通信距离大约为10~100米。

Bluetooth的基带符号速率为1Mb/s,采用数据包的形式按时隙传送,每时隙长0.625ūs,不排除将来采用更高的符号速率。Bluetooth系统支持实时的同步面向连接传输和非实时的异步面向非连接传输,分别成为SCO链路(Synchronous Ccnnection-Oriented Link)和ACL链路(Asynchronous Connection-Less Link),前者只要传送语音等实时性强的信息,在规定的时隙传输,后者则以数据为主,可在任意时隙传输。但当ACL传输占用SCO的预留时隙,一旦系统需要SCO传输,ACL则自动让出这些时隙以保证SCO的实时性。数据包被分成3大类:链路控制包、SCO包和ACL包。已定义了4钟链路控制数据包,后两者最多可分别定义12种,目前已定义了4种和7种,即共定义了15种。大多数数据包只占用1个时隙,但有些包占用3个或5个时隙。

Bluetooth支持64kb/s的实时语音传输和各种速率的数据传输,语音编码采用对数PCM或连续可变斜率增量调制(CVSD,Continuous Variable Slope Delta Modulation)。语音和数据可单独或者同时传输。当仅传输语音时,Bluetooth设备最多可同时支持3路全双工的语音通信;当语音和数据同时传输或仅传输数据时,Bluetooth设备支持433.9kb/s的对称全双工通信或723.2/57.6kb/s的非对称双工通信,另外,采用CRC(Cyclic Redundancy Check)、FEC(Forward Error Correction)及ARQ(Automatic Repeat Request),保证了通信的可靠性。

跳频是Bluetooth使用的关键技术之一,对应于单时隙包,Bluetooth的跳频速率为1600跳每秒;对应于多时隙包,跳频速率有所降低;但在建链时(包括寻呼和查询)则提高为3200跳每秒。使用这样高的跳频速率,Bluetooth系统具有足够高的抗干扰能力。

跳频序列受控于Bluetooth 48-bit设备地址码(BD——ADDR)中的28-bit和28-bit的时钟,采用以多级碟形运算为核心的映射方案。该跳频方案和其他方案相比,具有硬件设备简单、性能优越、便于79/23频段两种系统的兼容以及各种状态的跳频序列使用统一的电路来实现等特点。

3.2组网技术

Bluetooth根据网络的概念提供点对点和点对多点的无线链接,在任意一个有效通信范围内,所有设备的地位都是平等的。首先提出通信要求的设备称为主设备(Master),被动进行通信的设备称为从设备(Slave)。利用TDMA,一个Master最多可同时与7个Slave进行通信并和多个Slave(最多可超过200个)保持同步但不通信。一个Master和一个以上的Slave构成的网络称为Bluetooth的主从网络(Piconet)。若两个以上的Piconet之间存在着设备间的通信,则构成了Bluetooth的分散网络(Scatternet)。基于TDMA原理和Bluetooth设备的平等性,任意Bluetooth设备在Piconet和Scatternet中,既可作Master,又可作Slave,还可同时既是Master又是Slave。因此,在Bluetooth中没有基站的概念。另外,所有设备都是可移动的。

Bluetooth的基本出发点是可使其设备能够在全球范围内应用于任意的小范围通信。任一Bluetooth设备,都可根据IEEE 802标准得到一个唯一的48-bit的BD_ADDR,它是一个公开的地址码,可以通过人工或自动进行查询。在BD_ADDR基础上,使用一些性能良好的算法可获得各种保密和安全码,从而保证了设备识别码(ID,Identification)在全球的唯一性,以及通信过程中设备的鉴权和通信的安全保密。

4.蓝牙规范

蓝牙规范(Specification of the Bluetooth System)就是蓝牙无线通信协议标准,它规定了蓝牙应用产品应遵循的标准和需要达到的要求,由SIG颁布。

蓝牙规范包括核心协议(Core)与应用框架(Profiles)两个文件。协议规范部分定义了蓝牙的各层通信协议,应用框架指出了如何采用这些协议实现具体的应用产品。蓝牙协议规范遵循开放系统互连参考模型(Open System Interconnetion/Referenced Model, OSI/RM),从低到高地定义了蓝牙协议堆栈的各个层次。

5.协议堆栈

5.1概述

蓝牙栈的目的是什么呢?栈是控制蓝牙设备的软件(和固件)。蓝牙协议堆栈低层为各类应用所通用,高层则视具体应用而有所不同,大体上分为计算机背景和非计算机背景两种方式,前者通过主机控制接口(HCI,Host Controller Interface)实现高、低层的联接,后者则不需用HCI。层次结构使其设备具有最大可能的通用性和灵活性。根据通信协议,各种Bluetooth设备无论在任何地方,都可以通过人工或自动查询来发现其他Bluetooth设备,从而构成Piconet或Scatternet,实现系统提供的各种功能,使用起来十分方便。

5.2协议体系结构

蓝牙协议堆栈按照功能分为4层:

核心协议层(BaseBand、LMP、L2AP、SDP)
线缆替换协议层(RFCOMM)
电话控制协议层(TCS-BIN、AT命令集)
选用协议层(PPP、TCP、IP、UDP、OBEX、IrMC、WAP、WAE)

这里写图片描述
除上述协议层外,规范还定义了主机控制器接口(HCI),它为基带控制器、连接管理器、硬件状态和控制寄存器提供命令接口。在图1中,HCI位于L2CAP的下层,但HCI也可位于L2CAP上层。

蓝牙核心协议由SIG制定的蓝牙专用协议组成。绝大部分蓝牙设备都需要核心协议(加上无线部分),而其他协议则根据应用的需要而定。总之,电缆替代协议、电话控制协议和被采用的协议在核心协议基础上构成了面向应用的协议。

5.2.1核心协议

核心协议包括基带、链路管理、逻辑链路控制和适应协议四部分。

(1)基带(BaseBand)协议

描述了完成底层链路建立维护和执行基带协议的链路控制器的规范;基带和链路控制层确保微微网内各蓝牙设备单元之间由射频构成的物理连接。蓝牙的射频系统是一个跳频系统,其任一分组在指定时隙、指定频率上发送。它使用查询和分页进程同步不同设备间的发送频率和时钟,为基带数据分组提供了两种物理连接方式,即面向连接(SCO)和无连接(ACL),而且,在同一射频上可实现多路数据传送。ACL适用于数据分组,SCO适用于话音以及话音与数据的组合,所有的话音和数据分组都附有不同级别的前向纠错(FEC)或循环冗余校验(CRC),而且可进行加密。此外,对于不同数据类型(包括连接管理信息和控制信息)都分配一个特殊通道。

可使用各种用户模式在蓝牙设备间传送话音,面向连接的话音分组只需经过基带传输,而不到达L2CAP。话音模式在蓝牙系统内相对简单,只需开通话音连接就可传送话音。

(2)链路管理协议(Link Manager Protocol)

负责蓝牙组件间连接的建立,定义了链路的建立与控制的规范,在接收层信号由解释及过滤;该协议负责各蓝牙设备间连接的建立。它通过连接的发起、交换、核实,进行身份认证和加密,通过协商确定基带数据分组大小。它还控制无线设备的电源模式和工作周期,以及微微网内设备单元的连接状态。

(3)逻辑链路控制与适配协议(Logical Link Control and Adaptation Protocol)

位于基带(BaseBand)协议层上,属于数据链路层,是一个为高层传输和应用层协议屏蔽基带协议的适配协议,支持高层协议复用、数据包分段重组、QoS信息服务并获得相应的信息;该协议是基带的上层协议,可以认为它与LMP并行工作,它们的区别在于,当业务数据不经过LMP时,L2CAP为上层提供服务。L2CAP向上层提供面向连接的和无连接的数据服务,它采用了多路技术、分割和重组技术、群提取技术。L2CAP允许高层协议以64k字节长度收发数据分组。虽然基带协议提供了SCO和ACL两种连接类型,但L2CAP只支持ACL。

(4) 服务发现协议(SDP)

在蓝牙技术框架中起着至关紧要的作用,它是所有用户模式的基础。使用SDP可以查询到设备信息和服务类型,从而在蓝牙设备间建立相应的连接。

5.2.2电缆替代协议

电缆替代协议(RFCOMM)是ETSITS07.10的子集,提供L2CAP之上的串口防真。它在蓝牙基带协议上仿真RS-232控制和数据信号,为使用串行线传送机制的上层协议(如OBEX)提供服务。

5.2.3电话控制协议层

(1) 二元电话控制协议(TCS-Binary或TCSBIN)是面向比特的协议,它定义了蓝牙设备间建立语音和数据呼叫的控制信令,定义了处理蓝牙TCS设备群的移动管理进程。基于ITU TQ.931建立的TCSBinary被指定为蓝牙的二元电话控制协议规范。

(2)AT命令集电话控制协议,SIG定义了控制多用户模式下移动电话和调制解调器的AT命令集,该AT命令集基于ITU TV.250建议和GSM07.07,它还可以用于传真业务。

5.2.4选用协议

选用协议都是已有的其他组织的协议。

6.应用模型

Profiles部分规定不同蓝牙应用所需的协议,整个Profiles部分涉及了从耳机到局域网接入点等多种应用。对于每一个应用,应用模型给出其协议栈结构,并针对每一层具体规定一些必须实现的内容,诸如消息序列、功能集以及空中接口。依据应用模型实现应用,有利于不同厂家设备之间的互通性。

例如应用模型的第一个是一般接入应用模型,这个模型定义了用于发现蓝牙设备的过程、用于链接管理的过程、使用不同安全级别的过程,并描述了用户界面层次上参数的表示格式。模型首先给出了协议栈结构,而后分别描述了蓝牙地址、蓝牙设备类型、蓝牙PIN码在用户层面的表示格式,同时就认证等安全方面的内容给出流程图,最后描述设备发现及链路维护的消息序列,依照这个模型实现的设备互相可以发现对方并根据用户需求建立链路。

Bluetooth的四种应用模式

(1)通用访问应用(GAP)模式:定义了两个蓝牙单元如何互发现和建立连接,它是用来处理连接设备之间的相互发现和建立连接的。它保证两个蓝牙设备,不管是哪一家厂商的产品,都能够发现设备支持何种应用,并能够交换信息。

(2)服务发现应用(SDAP)模式:定义了发现注册在其他蓝牙设备中的服务的过程,并且可以获得与这些服务相关的信息。

(3)串口应用(SPP)模式:定义了在两个蓝牙设备间基于RFCOMM建立虚拟的串口连接的过程和要求。

(4)通用对象交换应用(GOEP)模式:定义了处理对象交换的协议和步骤,文件传输应用和同步应用都是基于这一应用的,笔记本电脑、PDA、移动电话是这一应用模式的典型应用。

7.蓝牙版本

根据不同的蓝牙版本,传输速度会差很多。例如蓝牙3.0的传输速度为3Mb/s,而蓝牙4.0技术从理论上可以达到60Mb/s。到目前为止,蓝牙最新版本为4.0,它的版本历史如下表所示:

版本 规范发布日期 增强功能
0.7 1998年10月19日 Baseband、LMP
0.8 1999年1月21日 HCI、L2CAP、RFCOMM
0.9 1999年4月30日 OBEX与IrDA的互通性
1.0Draft 1999年7月5日 SDP、TCS
1.0A 1999年7月26日 /
1.0B 2000年10月1日 WAP应用上更具互通性
1.1 2001年2月22日 IEEE 802.15.1
1.2 2003年11月5日 列入IEEE 802.15.1a
2.0+EDR 2004年11月9日 EDR传输率提升至2-3Mbps
2.1+EDR 2007年7月26日 简易安全配对、暂停与继续加密、Sniff省电
3.0+HS 2009年4月21日 交替射频技术、取消了UMB的应用
4.0+HS 2010年6月30日 传统蓝牙技术、高速蓝牙和新的蓝牙低功耗技术

表X 蓝牙规范版本历史

8.Android对蓝牙的支持状况

8.1支持版本

蓝牙技术作为目前比较常用的无线通信技术,早已成为手机的标配之一,可以实现蓝牙手机互连、传输文件、蓝牙耳机、蓝牙键盘等等功能。Android1.5对蓝牙的支持非常不完善,只支持像蓝牙耳机一样的设备,并不支持蓝牙数据传输等高级特性。在Android2.0终于加入了完善的蓝牙支持。

8.2Android蓝牙应用开发

8.2.1应用开发基本功能

在Android开发者网站http://developer.android.com/guide/topics/connectivity/bluetooth.html,介绍Android平台包括支持蓝牙的协议栈,允许设备之间通过无线交换数据。应用框架层提供了实现蓝牙功能的API,这些API使得应用可以无线连接到其他蓝牙设备,提供点对点和多点无线互连功能。通过蓝牙API,Android应用能够实现下面的功能:

 扫描其他蓝牙设备
 检索配对的蓝牙设备
 建立RFCOMM连接
 与其他设备传递数据
 管理多路连接

8.2.2 应用开发API

使用Android API进行蓝牙通信,需要完成四个主要工作:建立蓝牙,寻找附近配对的或者可以使用的蓝牙设备,连接设备,在设备间传递数据。
所有的API都在android.bluetooth包中,在建立蓝牙连接中所需要使用的类和接口如下:

BluetoothAdapter:代表本地蓝牙适配器(Bluetooth Radio)。该BluetoothAdapter是入口点的所有蓝牙互动。利用这一点,你会发现其他蓝牙设备,查询保税(配对)的设备列表,使用已知的MAC地址实例化一个BluetoothDevice类,并创建一个BluetoothServerSocket来监听来自其他设备的通信。

BluetoothDevice:代表一个远程蓝牙设备。使用它可以请求与远程设备的连接,通过一个BluetoothSocket或者关于设备的名称,地址,类别和键合状态的查询信息。

BluetoothSocket:代表了一个蓝牙套接字(类似于TCP套接字)的接口。这是一个连接点,允许应用程序通过InputStream和OutputStream与其他蓝牙设备交换数据。

BluetoothServerSocket:代表一个侦听传入的请求的开放服务器套接字(类似于TCP的ServerSocket)。为了连接两个Android设备,一台设备必须打开这个类服务器套接字。一个远程蓝牙设备发送连接请求到该设备,当连接被接受后BluetoothServerSocket会返回一个连接的BluetoothSocket。

BluetoothClass:描述蓝牙设备的一般特点和能力。这是一组只读的定义了设备主要和次要的类以及服务的属性。然而,这并不能可靠地描述所有的蓝牙协议和设备支持的服务,但作为一个提示的设备类型是有用的。

BluetoothProfile:一个表示蓝牙协议的接口。蓝牙协议是一个设备之间基于蓝牙通信的无线接口规范。比如免提协议(Hands-Free)。
BluetoothHandset:对蓝牙耳机可被手机使用提供了支持。包括蓝牙耳机(Bluetooth Headset)协议和免提(Hands-Free V1.5)协议。

BluetoothA2dp:定义了高品质的音频怎样通过蓝牙连接以流的方式从一个设备传输到另一个设备。“A2DP”代表高级音频传输模式(Advanced Audio Distribution Profile)。

BluetoothHealth:代表控制蓝牙服务的健康设备协议代理。

BluetoothHealthCallback:用来实现BluetoothHealth回调的抽象类。你必须继承这个类并实现回调方法来接收有关更改应用程序的注册状态和蓝牙信道状态的更新。

BluetoothHealthAppConfiguration:表示蓝牙健康第三方应用程序注册与远程蓝牙健康设备进行通信的应用程序配置。

BluetoothProfile. ServiceListener:当BluetoothProfile IPC客户端已经连接到或从服务(一个运行特定协议的内部服务)断开连接时,通知该客户端的接口。

至于具体如何实现各种基本功能,本文暂不赘述。

8.3Android蓝牙源码开发

8.3.1Android蓝牙架构

在Android源码开发网站https://source.android.com/devices/bluetooth.html,介绍蓝牙如下:

Android提供了一个默认的蓝牙协议栈:BlueDroid,它被分为两层:蓝牙嵌入式系统(BTE),它实现了核心的蓝牙功能;和蓝牙应用层(BTA),它与蓝牙的应用框架进行通信。一个蓝牙系统服务通过JNI与蓝牙协议栈进行通信,通过Binder IPC与应用程序进行通信。蓝牙系统服务向开发者提供了多种访问蓝牙协议的途径。下图显示了蓝牙协议栈的整体结构:
这里写图片描述
Application framework
在应用程序框架层是应用程序的代码,它利用android.bluetooth API与蓝牙硬件交互。在内部,这个代码通过Binder IPC机制调用蓝牙进程。

Bluetooth system service
蓝牙系统服务,位于packages/apps/Bluetooth,被打包为一个Android应用程序,并在Android框架层实现了蓝牙服务和协议。这个应用程序通过JNI调用到HAL层。

JNI
与android.bluetooth相关的JNI代码位于packages/apps/Bluetooth/jni。JNI代码调用到HAL层,并且当某些蓝牙操作发生,比如当设备被发现的时候,接收来自HAL层的回调。

HAL
硬件抽象层定义了android.bluetooth API和蓝牙进程需要调用到,而且必须实现以确保蓝牙硬件能正确运行的标准接口。对于蓝牙HAL的头文件位于hardware/libhardware/include/hardware/bluetooth.h和hardware/libhardware/include/hardware/bt_*.h文件中。

Bluetooth stack
默认的蓝牙协议栈是提供给开发者的,位于external/bluetooth/bluedroid。该协议栈实现了通用的蓝牙HAL,当然,通过扩展和更改配置也可以自定义它。

Vendor extensions
用来添加用于跟踪的自定义扩展和HCI层,你可以创建一个libbt-vendor模块,并指定这些组件。

8.3.2实现HAL

蓝牙HAL位于hardware/libhardware/include/hardware/,它包含以下头文件:

bluetooth.h 包含设备上的蓝牙硬件HAL
bt_av.h 包含高级音频协议HAL
bt_hf.h 包含免提协议HAL
bt_hh.h 包含HID主机协议HAL
bt_hl.h 包含健康协议HAL
bt_pan.h 包含pan协议HAL
bt_sock.h 包含套接字协议HAL

请记住,你的蓝牙实现并不限制在HAL暴露的功能和协议。你可以找到位于external/bluetooth/bluedroid目录的默认实现,它实现了默认的HAL,也实现了额外的和自定义的功能。

8.3.3定制BlueDroid协议栈

如果你在使用默认的BlueDroid协议栈,但是想做一小部分定制,那么可以通过以下方式:

(1)定制蓝牙协议 - 如果你想补充的是, Android的HAL接口所没有提供的蓝牙协议,你必须提供一个SDK插件下载,使协议可以被程序开发人员使用,使蓝牙系统进程应用程序可以使用该API(packages/apps/Bluetooth),并把它们添加到BlueDroid堆栈(external/bluetooth/bluedroid)。

(2) 定制厂商扩展和配置更改 - 你可以通过创建一个libbt-vendor模块来添加东西,例如额外的AT命令或特定于设备的配置更改。可以去vendor/broadcom/libbt-vendor目录查看示例。

(3) 主机控制器接口(HCI) - 你可以通过创建一个libbt-HCI模块来提供自己的人机交互,它主要用于调试跟踪。可以去external/bluetooth/hci目录查看示例。

9.Android手机蓝牙代码剖析

下面,将以MTK6572平台的手机进行说明。

9.1手机蓝牙的外在功能点

(可画一幅功能图)

蓝牙开启与关闭
Rename phone
Visibility timeout
show received files
蓝牙配对
蓝牙传输文件

9.2蓝牙源代码位置

蓝牙核心功能的代码主要位于以下路径:

alps/packages/apps/Settings/src/com.android.settings.bluetoothangel
alps/frameworks/base/core/java/android/bluetooth
alps/mediatek/packages/apps/Bluetooth
alps/mediatek/frameworks-ext/base/core/java/android/bluetooth
alps/mediatek/frameworks-ext/base/core/java/android/server
alps/mediatek/frameworks-ext/base/core/jni/

核心代码结构如下表所示:

Module Sourcefile
Settings alps/packages/apps/Settings/src/com/android/settings/bluetoothangel/ All files
Phone alps/packages/apps/Phone/src/com/mediatek/blueangel/ All files
Media Play alps/packages/apps/Music/src/com/mediatek/bluetooth/avrcp/ All files
Other profile(opp、avrcp) alps/mediatek/packages/apps/Bluetooth/profiles/ All files

蓝牙核心代码结构表
表X 蓝牙核心代码结构表

9.3蓝牙开启与关闭

蓝牙开关
蓝牙开关
蓝牙开关
蓝牙开关
图X 蓝牙开关

手机蓝牙默认是关闭的,要想打开蓝牙,可以通过上图所示的四个地方:

(1)手机设置里的蓝牙选项,后面有个开关,点击可以进行打开/关闭操作;
(2)点进去蓝牙设置界面,右上角也有个开关,点击可以进行打开/关闭操作;
(3)在手机下拉快捷设置界面,长按【BLUETOOTH】按钮,可以进入蓝牙设置界面,后同第(2)条;
(4)在进行文件的分享操作时,可以选择通过蓝牙进行传送,则会弹出是否打开蓝牙的对话框。
蓝牙打开/关闭时序图
图X 蓝牙打开/关闭时序图

在Settings.java中,如果检测到Bluetooth service不可用,则不会显示蓝牙设置选项。具体代码如下:

      } else if (id == R.id.bluetooth_settings) {
                // Remove Bluetooth Settings if Bluetooth service is not available.
                if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
                    target.remove(i);
                }

简单讲一下Settings界面的实现原理,它继承于PreferenceActivity,每一个设置项被定义为一个Header。但是由于有特殊的项,比如Wi-Fi、Bluetooth右侧有一个开关,Quick start右侧有一个勾选框,以及若干项归为一类,不同分类之间有间隔,因此,这些项用如下四种标签来区分:

        static final int HEADER_TYPE_CATEGORY = 0;
        static final int HEADER_TYPE_NORMAL = 1;
        static final int HEADER_TYPE_SWITCH = 2;
        static final int HEADER_TYPE_CHECK = 3;

Wi-Fi和Bluetooth右侧开关为Switch组件,实际上是一个CompoundButton,分别使用WifiEnabler和BluetoothEnabler管理;Quick start右侧的勾选框为一个CheckBox。Header的视图,则由Settings.java的内部类HeaderAdapter.java来管理,具体每一个Header的各个组件,在HeaderAdapter.java的内部类HeaderViewHolder.class中进行定义,代码如下:

        private static class HeaderViewHolder {
            ImageView icon;
            TextView title;
            TextView summary;
            Switch switch_;
            //FR441047-qixing.chen-001 begin
            CheckBox check;
            //FR441047-qixing.chen-001 end
        }

这样,在getView()方法中,通过getHeaderType()方法获得Header类型,然后使用LayoutInflater装载不同Header视图,即形成我们最终看到的Settings主界面。
在BluetoothEnabler.java中,实现了setSwitch()方法,该方法会读取当前蓝牙的当前开关状态bluetoothState,并根据这个状态来设置Switch组件的选中状态(setChecked)和是否可选(setEnabled)状态。当Switch的选中状态有改变的时候,将调用onCheckedChanged()方法,然后通过setBluetoothEnabled()继续向下调用。代码如下:

       /// M: if receive bt status changed broadcast, do not need enable/disable bt.
        if (mLocalAdapter != null && !mUpdateStatusOnly) {
            mLocalAdapter.setBluetoothEnabled(isChecked);
        }

这里有一个mUpdateStatusOnly的变量十分重要,它是BluetoothEnabler.java的全局变量,用来表示是否只更新Switch的状态。设置它的目的是我们可能通过其他途径(蓝牙开关的第四幅图)打开或者关闭蓝牙,而这种情况下必须同步更改Switch的状态,但此时是不需要再次打开/关闭蓝牙的操作的。

LoacalBluetoothAdapter.java中实现setBluetoothEnabled()方法。这个方法重要之处在于,它清晰地提供了打开/关闭代码逻辑上的分支,看下面代码很容易理解:

public void setBluetoothEnabled(boolean enabled) {
        boolean success = enabled
                ? mAdapter.enable()
                : mAdapter.disable();
        if (success) {
            setBluetoothStateInt(enabled
                ? BluetoothAdapter.STATE_TURNING_ON
                : BluetoothAdapter.STATE_TURNING_OFF);
        } else {
            if (Utils.V) {
                Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
                        "success for enabled: " + enabled);
            }
            syncBluetoothState();
        }
    }

如果传递过来的参数enabled是true,则表示打开蓝牙,就调用mAdapter.enable();如果enabled是false,则表示关闭蓝牙,就调用mAdapter.disable()。流程图只画出了打开蓝牙的流程,而关闭蓝牙的流程从这之后,只要将enable环卫disable就基本可以了。

接下来进入BluetoothAdapter.java,其中分别实现了enable()和disable()方法,两个方法最终分别调用了BluetoothManagerService.java中的enable()和disable()方法。

在BluetoothManagerService.java中,enable()和disable()方法分别调用sendEnableMsg(false)和sendDisableMsg(),而这两个本地方法有异曲同工之妙,就是最终都发送了消息,由BluetoothHandler来处理。代码如下:

private void sendDisableMsg() {
        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE));
    }
    private void sendEnableMsg(boolean quietMode) {
        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
                             quietMode ? 1 : 0, 0));
    }

BluetoothHandler是BluetoothManagerService.java的内部类,handleMessage()方法中即实现了对蓝牙各个不同状态的消息处理。找到对应的分支,接下来调用本地方法handleEnable()和handleDisable()两个方法。

在handleEnable()中,通过mBluetooth.enable()或mBluetooth.enableNoAutoConnect()继续向下调用;在handleDisable()中,通过mBluetooth.disable(false)继续向下调用。

上面三个方法都调用自BluetoothService.java,分别实现了enable()和disable()的带参数重载。并在其中分别发送了消息进行打开关闭的操作,代码如下:
enable:

        mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON, saveSetting);

disable:

        mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_OFF, persistSetting);

mBluetoothState为BluetoothAdapterStateMachine.java的实例,在它的内部类PowerOff中,processMessage()方法对消息进行处理。在消息处理的对应代码分支,将调用prepareBluetooth()本地方法。在该方法中,通过mBluetoothService调用enableNative()和disableNative()方法。mbluetoothService为BluetoothService的实例,而enableNative()和disableNative()则是JNI层的方法。

在蓝牙开关图第二幅中,由于都使用的是同一个BluetoothEnabler作为入口,所以所有流程都一样。

在蓝牙开关图第三幅中,只需要搞懂手机下拉快捷设置中关于蓝牙的界面部分。

它的代码在/mtk6572-v1.0-dint/frameworks/base/packages/SystemUI/src下面。

作用
packages.SystemUI.src.com.android.systemui.statusbar.phone QuickSettings.java
packages.SystemUI.src.com.android.systemui.statusbar.policy BluetoothController.java
packages.SystemUI.src.com.android.systemui.statusbar.toolbar QuickSettingsConnectionModel.java

表X 蓝牙快捷设置代码结构表

这部分涉及SystemUI模块,由于时间因素暂时先不细讲。

在蓝牙开关图第四幅中,也待补充。

9.4蓝牙设置主界面

蓝牙各种功能的配置,主要集中在蓝牙设置主界面。它包括了打开/关闭,重命名,可见时间设置,共享历史查询,搜索周围设备,配对周围设备,配对后进行文件传输等功能。也就是说,以上功能都能在这里找到入口。
蓝牙设置主界面(关闭状态)
图X 蓝牙设置主界面(关闭状态)

蓝牙设置,对应的代码位置在Settings模块下的com.android.settings.bluetoothangel包内。上图的蓝牙设置界面,主要由BluetoothSettings.java负责。接下来的很多功能,都将从这个类开始。

当打开蓝牙后,菜单栏的【Rename phone】和【Visibility timeout】就会变为可选状态。在最顶端会显示本机蓝牙设备名称和可见状态。默认对所有其他蓝牙设备不可见。
蓝牙设置主界面(打开状态)
图X 蓝牙设置主界面(打开状态)

9.5蓝牙设备重命名

蓝牙设备都会拥有自己的名字,便于用户识别。对于手机这类可进行输入的蓝牙设备,还可以更改它的名字。当周围设备搜索到该设备后,所看到的名字即这个修改后的名字。

点击【Rename phone】,将出现一个对话框,如下图所示:
蓝牙rename功能图
图X 蓝牙rename功能图

由BluetoothNameDialogFragment.java负责rename界面,是一个Dialog。

其中,需要特别注意两点:

(1)修改确认的【Rename】按钮,在没有进行修改的时候不可按;在有了修改操作后变为可按。为了区分有没有修改,使用 mDeviceNameEdited作为区分标志。还有一个标志为mDeviceNameUpdated,则是用来标记是否有过确认操作。

(2)在更改名字的这个界面,有可能发生转屏,转屏是需要销毁Activity再重新建立Activity的。因此,必须处理这种特殊情况下的名字保存问题,使用了KEY_NAME和KEY_NAME_EDITED两个静态关键字,来保存修改中的名字和修改的状态,从而在发生转屏的时候能够恢复该名字信息。

更改的名字,最终保存在手机data/@mtBluetooth路径下面。
蓝牙rename时序图
图X 蓝牙rename时序图

9.6蓝牙可见时间设置

蓝牙在开启后,默认是不可见状态,也就是说就算开启蓝牙,周围的其他设备也无法搜索到你的设备。要想能够被其他设备搜索得到,必须设置“可见时间”。一般可以设置可见2分钟到数分钟不等,在这种情况下,设备在设置的时间范围内对外可见,超过时间后自动变为不可见;当然,也可以设置为一直可见,这样就不会有时间限制;但是,每次重新关闭又打开蓝牙后,都必须重新手动点击“可见时间”选项,才能重新生效(这种行为方式作者认为可以被定制)。

这部分内容,将功能截图与代码分析放在一起,便于对照说明。整个流程的时序图如下:
蓝牙可见时间设置时序图
图X 蓝牙可见时间设置时序图

点击【Visibility timeout】后,显示如下界面:
蓝牙Visibility timeout设置界面
图X 蓝牙Visibility timeout设置界面

(1) 该界面由BluetoothVisibilityTimeoutFragment.java负责。这是一个填充了列表的dialog,几个数据选项来自资源com.android.settings.R.array.bluetooth_visibility_timeout_entries。核心代码如下:

    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.bluetooth_visibility_timeout)
                .setSingleChoiceItems(R.array.bluetooth_visibility_timeout_entries,
                        mDiscoverableEnabler.getDiscoverableTimeoutIndex(), this)
                .setNegativeButton(android.R.string.cancel, null)
                .create();
    }

(2)array默认选项为2minutes。在初始化该dialog时的mDiscoverableEnabler.getDiscoverableTimeoutIndex()方法中进行控制,