Bluetooth--- android -- 蓝牙 bluetooth

来源:互联网 发布:yum的配置 编辑:程序博客网 时间:2024/04/24 08:19

android -- 蓝牙 bluetooth (一) 入门

分类: Android的原生应用分析 6229人阅读 评论(37) 收藏 举报
bluetooth4.2.2源码android

目录(?)[+]

        前段时间在 网上看了一些关于android蓝牙的文章,发现大部分是基于老版本(4.1以前含4.1)的源码,虽然无碍了解蓝牙的基本原理和工作流程,但对着4.2.2的代码看起来总是有些遗憾。所以针对4.2.2版本代码整理下相关知识,当然蓝牙工作的主干流程是没有变的,上电、加载驱动这些动作少不了的,只是这些功能的实现代码位置变了不少。希望本文可以让大家对android4.2的蓝牙部分代码有一个初步的了解。


        正文开始前,先明确代码版本:android  jellyBean 4.2.2,后续的蓝牙相关文章同样如此。

        另推荐个源码在线阅读网址 http://androidxref.com/,已经知道的童鞋无视这行吧。


        入手一个新的模块或应用,当然首先要知道它都有什么了,与它相关的代码在那里,所以先一起看下蓝牙代码分布吧。


1. 代码分布:

    packages/apps/Bluetooth/     

         看这路径肯定是蓝牙应用方面的代码了,主要是关于蓝牙应用协议的表现代码,包括opp、hfp、hdp、a2dp、pan等等,这些名词后面再解释。

    frameworks/base/core/java/android/server/  

          4.2以后这个目录虽然还有了,但里面代码已经转移到应用层了,就是前面那个目录,所以4.2.2上的蓝牙这里可以忽略。

    framework/base/core/java/android/bluetooth  

           这个目录里的代码更像一个桥梁,里面有供java层使用一些类,也有对应的aidl文件联系C、C++部分的代码,还是挺重要的。

    kernel\drivers\bluetoothBluetooth    

           具体协议实现。包括hci,hid,rfcomm,sco,SDP等协议

    kernel\net\bluetooth Linux kernel

           对各种接口的Bluetoothdevice的驱动。例如:USB接口,串口等,上面kernel这两个目录有可能看不到的,但一定会有的。

    external\bluetooth\bluedroid      BlueZ (应用空间协议),官方蓝牙协议栈。

    system\bluetoothBluetooth        适配层代码,和framework那个作用类似,是串联framework与blueZ的工具。

        大致代码分布就是这些,初步查看后让我们再来看下蓝牙的整体结构。


2.整体结构:

        这部分直接上图了,看着直观些。图中把JNI部分虽然在目前4.2的代码中在packages层,这里还是画在Framework层了,说明下希望

不要引起理解的误会。从图上可以感觉到整体流程和以前变化不大,所以流程方面的文章看4.1或更早的应该问题也不大。



 PS:上图关于蓝牙协议栈的说明有误,4.2里已经不再是bluez了,在些更正一下,当然协议栈这一部分还是要有的,新的协议栈看下面英文:


Android 4.2 introduces a new Bluetooth stack optimized for use with Android devices. The new Bluetooth stack developed in 

collaboration between Google and Broadcom replaces the stack based on BlueZ and provides improved compatibility and reliability.

google和broadcom合作开发了一个新蓝牙协议栈,老版本的兼容性问题在所难免了。在此感谢网友andger032的提醒。


3.常用类和名词解释:  

   \packages\apps\Settings\src\com\android\settings\bluetooth 目录下

     BluetoothEnabler.java   界面上蓝牙开启、关闭的开关就是它了, 

     BluetoothSettings.java  主界面,用于管理配对和连接设备

     LocalBluetoothManager.java  提供了蓝牙API上的简单调用接口,这里只是开始。

    CachedBluetoothDevice.java   描述蓝牙设备的类,对BluetoothDevice的再封装

    BluetoothPairingDialog.java  那个配对提示的对话框


  /packages/apps/Phone/src/com/android/phone/

    BluetoothPhoneService.java  在phone的目录肯定和电话相关了,蓝牙接听挂断电话会用到这个


 /packages/apps/Bluetooth/src/com/android/bluetooth/btservice/

        AdapterService.java    4.2后才有的代码,蓝牙打开、关闭、扫描、配对都会走到这里,其实更准确的说它替代了4.1之前的BluetoothService.java,原来的工作就由这个类来完成了。说到这里不能不说4.2蓝牙的目录变了,在4.1及以前的代码中packages层的代码只有opp协议相关应用的代码,也就是文件传输那部分,而4.2的代码应用层的代码则丰富了许多,按具体的蓝牙应用协议来区别,分为以下文件夹(这里一并对蓝牙一些名词作个简单解释):

       a2dp    蓝牙立体声,和蓝牙耳机听歌有关那些,另还有个avrcp--音频/视频远程控制配置文件,是用来听歌时暂停,上下歌曲选择的。
       btservice  这个前面AdapterService.java的描述大家应该能猜到一些,关于蓝牙基本操作的目录,一切由此开始。
       hdp      蓝牙关于医疗方面的应用 Bluetooth Health Device Profile
       hfp       和电话相关,蓝牙接听、挂断电话  Hands-free Profile
       hid      人机交互接口,蓝牙鼠标键盘什么的就是这个了
      opp     不多解释,以前就有。
      pan      描述了两个或更多个 Bluetooth 设备如何构成一个即时网络,和网络有关的还有串行端口功能(SPP),拨号网络功能(DUN)

      pbap    电话号码簿访问协议(Phonebook Access Profile)

       android 4.2的蓝牙应用层部分代码更丰富了,虽然有些目录还没具体代码,不过说不准哪个版本更新就有了,就像4.0添加了hdp医疗那部分一样。

另外原本在framework的JNI代码也被移到packages/apps/bluetooth当中。     

   /frameworks/base/core/java/android/bluetooth/目录下


      BluetoothA2dp.java A2DP的功能实现
     BluetoothAdapter.java 蓝牙action的定义,虚拟设备属性以及操作方法
     BluetoothAudioGateway.java 蓝牙语音网关
     BluetoothClass.java 蓝牙设备类型的定义
     BluetoothDevice.java 蓝牙设备属性
     BluetoothDevicePicker.java 定义远程蓝牙设备的特性,比如需要认证,设备类型
     BluetoothHeadset.java 定义蓝牙headset功能的属性以及接口
     BluetoothInputStream.java 蓝牙流接口的实现(输入流)
    BluetoothOutputStream.java 蓝牙流接口的实现(输出流)
    BluetoothServerSocket.java 蓝牙socket服务端具备的方法
    BluetoothSocket.java 蓝牙socket的封装
    BluetoothUuid.java 蓝牙uuid的定义以及uuid的解析

    以上java文件在使用具体功能会用到,现在只是简单描述下,至于具体使用在后续文章用到时再给出。同时代码说明部分也就写这些了

对于C、C++部分的代码一方面没看那么多,另一方面根据android JNI的命名习惯,大家找起来也很容易。


4.后续分析:

       前面从整体上描述蓝牙的基本知识,落实在具体的代码分析上,我们按几个主线功能来走,蓝牙的开关、搜索配对、蓝牙耳机与电话和文件传输,

这几个也算是蓝牙的常用必备功能了,所以在后续文章中将按着这个顺序来跟一下它们代码调用流程。希望可以让你快速的了解蓝牙,当然如果有失误

写错的地方,欢迎反馈,谢谢。
 
 

android -- 蓝牙 bluetooth (二) 打开蓝牙

分类: Android的原生应用分析 6748人阅读 评论(21) 收藏 举报
androidbluetooth打开蓝牙BT4.2

        4.2的蓝牙打开流程这一部分还是有些变化的,从界面上看蓝牙开关就是设置settings里那个switch开关,widget开关当然也可以,起点不同而已,后续的流程是一样的。先来看systemServer.java的代码,蓝牙服务开启的地方,最后一个else分支是我们关心的,前两个是模拟器的一个测试模式的。

[java] view plaincopy
  1. if (SystemProperties.get("ro.kernel.qemu").equals("1")) {                                            
  2.                Slog.i(TAG, "No Bluetooh Service (emulator)");                                     
  3.            } else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {                       
  4.                Slog.i(TAG, "No Bluetooth Service (factory test)");                                
  5.            } else {                                                                               
  6.                Slog.i(TAG, "Bluetooth Manager Service");                                          
  7.                bluetooth = new BluetoothManagerService(context);                                  
  8.                ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth);  
  9.            }     

         暂且看下bluetoothManagerService的构造方法,代码有点多,我们只看两个地方, loadStoredNameAndAddress()是读取蓝牙打开默认名称的地方,isBluetoothPersistedStateOn()是判断是否已打开蓝牙的,如果已打开,后续操作要执行开启蓝牙的动作,前面那几行注册广播其中就有这个作用。

[java] view plaincopy
  1. BluetoothManagerService(Context context) {  
  2.         ...一些变量声明初始化...  
  3.         IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);  
  4.         filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);  
  5.         filter.addAction(Intent.ACTION_USER_SWITCHED);  
  6.         registerForAirplaneMode(filter);  
  7.         mContext.registerReceiver(mReceiver, filter);  
  8.         loadStoredNameAndAddress();  
  9.         if (isBluetoothPersistedStateOn()) {  
  10.             mEnableExternal = true;  
  11.         }  
  12.     }  

        回到界面开关那个看得着的地方,界面上开关就是BluetoothEnabler.java这个类了,而setBluetoothEnabled()则是具体开关动作。看下代码

[java] view plaincopy
  1. public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {  
  2.        // Show toast message if Bluetooth is not allowed in airplane mode  
  3.        if (isChecked &&  
  4.                !WirelessSettings.isRadioAllowed(mContext, Settings.Global.RADIO_BLUETOOTH)) {  
  5.            Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();  
  6.            // Reset switch to off  
  7.            buttonView.setChecked(false);            
  8.        }  
  9.   
  10.        if (mLocalAdapter != null) {  
  11.            mLocalAdapter.setBluetoothEnabled(isChecked);  
  12.        }  
  13.        mSwitch.setEnabled(false);  
  14.    }  

这里在判断是飞行模式不知道为什么没有return,如果是飞行模式会有提示toast弹出,既然这样源码为什么还要执行下面打开流程呢,也许是个bug?不细究这个了,继续看setBluetoothEnabled()方法做什么了,很明显mLocalAdapter(LocalBluetoothAdapter )只是个过渡,里面的 mAdapter(BluetoothAdapter)才是真正的主角,代码如下:

[java] view plaincopy
  1. public void setBluetoothEnabled(boolean enabled) {  
  2.     boolean success = enabled   ? mAdapter.enable() : mAdapter.disable();  
  3.   
  4.     if (success) {  
  5.         setBluetoothStateInt(enabled  
  6.             ? BluetoothAdapter.STATE_TURNING_ON  
  7.             : BluetoothAdapter.STATE_TURNING_OFF);  
  8.     } else {  
  9.        .........  
  10.     }  
  11. }  
        在BluetoothAdapter.java里可以看到一个单例模式的应用,主要提供给其它程序调用蓝牙的一些方法用的,外部程序想调用蓝牙的方法就要先用这个

拿到BluetoothAdapter对象,代码也简单看下吧,里面是典型的binder应用。

[java] view plaincopy
  1. public static synchronized BluetoothAdapter getDefaultAdapter() {  
  2.    if (sAdapter == null) {  
  3.        IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);  
  4.        if (b != null) {  
  5.            IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);  
  6.            sAdapter = new BluetoothAdapter(managerService);  
  7.        } else {  
  8.            Log.e(TAG, "Bluetooth binder is null");  
  9.        }  
  10.    }  
  11.    return sAdapter;  
        此时我们更关心mAdapter.enable()的后续操作,外部其它应用到getDefaultAdapter()也是调用enable(),注意,到了BluetoothAdapter我们已经在framework层了,顺着BluetoothAdapter.java的enable()调用先回到BluetoothManagerService.java的enable(),再进一步来到BluetoothManagerService.java中的handleEnable()

方法,后面要跳转到新类了,贴出来一起看下,这部分好像不同版本还有些出入,不过核心的启动service是一样的,不影响理解。

[java] view plaincopy
  1.  private void handleEnable(boolean persist, boolean quietMode) {  
  2.  synchronized(mConnection) {  
  3.     if ((mBluetooth == null) && (!mBinding)) {  
  4.         //Start bind timeout and bind  
  5.         Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);  
  6.         mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);  
  7.         mConnection.setGetNameAddressOnly(false);  
  8.         Intent i = new Intent(IBluetooth.class.getName());  
  9.         if (!mContext.bindService(i, mConnection,Context.BIND_AUTO_CREATE,  
  10.                                   UserHandle.USER_CURRENT)) {  
  11.             mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);  
  12.             Log.e(TAG, "Fail to bind to: " + IBluetooth.class.getName());  
  13.         } else {  
  14.             mBinding = true;  
  15.         }  
  16.     }   
       下面跑到哪个service里去了呢,在log信息里可以看到"ActivityManager: Start proc com.android.bluetooth for service com.android.bluetooth/.btservice.AdapterService:"

这样的信息,那就是去AdapterService里看看,里面一共有三个enable(),跳转关系不复杂,我们直接看最后一个关键的。

[java] view plaincopy
  1. public synchronized boolean enable(boolean quietMode) {  
  2.      enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,  
  3.              "Need BLUETOOTH ADMIN permission");  
  4.      if (DBG)debugLog("Enable called with quiet mode status =  " + mQuietmode);  
  5.      mQuietmode  = quietMode;  
  6.      Message m =  
  7.              mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_ON);  
  8.      mAdapterStateMachine.sendMessage(m);  
  9.      return true;  
  10.  }  
 状态机来了,状态转换图,从一个状态接受命令跳到另一个状态,因为我们是在开启蓝牙,所以先去的AdapterState.java内部类offstate.java里面找,在这个分支USER_TURN_ON看到mAdapterService.processStart();在这里面可以看到蓝牙遍历下所支持的profile,最后又发出个带AdapterState.STARTED标识的消息

处理在同文件下面的代码里

[java] view plaincopy
  1. case STARTED:   {  
  2.   if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STARTED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);  
  3.   //Remove start timeout  
  4.   removeMessages(START_TIMEOUT);  
  5.   
  6.   //Enable  
  7.   boolean ret = mAdapterService.enableNative();  
  8.   if (!ret) {  
  9.       Log.e(TAG, "Error while turning Bluetooth On");  
  10.       notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);  
  11.       transitionTo(mOffState);  
  12.   } else {  
  13.       sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);  
  14.   }   

看到那个enableNative()函数调用了吧,又要用到JNI了,稍微回头看下前面的代码,我们先从应用界面开关BluetoothEnabler走到framework的BluetoothAdapter,又回到package的adapterService,现在又要去JNI的C++代码了,往常一般是packages -->framework-->下面一层,这次顺序有些颠倒了,不过这不能影响我们跟踪代码,最后

还是要到下面去的。一起往下看吧。

        根据android JNI的函数命名惯例很容易找到enableNative对应的C++函数在packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp里面

[java] view plaincopy
  1. static jboolean enableNative(JNIEnv* env, jobject obj) {  
  2.    ALOGV("%s:",__FUNCTION__);  
  3.    jboolean result = JNI_FALSE;  
  4.     if (!sBluetoothInterface) return result;  
  5.     int ret = sBluetoothInterface->enable();  
  6.     result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
  7.     return result;  
  8. }  

代码瞬间简洁了不少,看来更多的故事还在下面,sBluetoothInterface这是什么,直接关系到下一步去哪的问题,看下变量声明,原来是

Const bt_interface_t *sBluetoothInterface = NULL; 再去找在哪初始化,搜索external目录可以找到/external/bluetooth/bluedroid/btif/src/bluetooth.c

[cpp] view plaincopy
  1.     static const bt_interface_t bluetoothInterface = {  
  2.     sizeof(bt_interface_t),  
  3.     init,  
  4.     enable,  
  5.     disable,  
  6.     .............  
  7.     start_discovery,  
  8.     cancel_discovery,  
  9.     create_bond,  
  10.     remove_bond,  
  11.     cancel_bond,  
  12.    ...............  
  13. };  
原来在这里,说下怎么找到,直接跳转是不成了,看这个文件夹下的mk文件,那里面有libhardware目录是编译的时候要用到,这个多半在hardware目录里,在这里面很快可以看到bluetooth.h,那里面有最我们要找的结构体定义,头文件找到了,再找同名C文件就快了,好了继续吧看下enable()里是怎么实现的

[cpp] view plaincopy
  1. static int enable( void )  
  2. {  
  3.     ALOGI("enable");  
  4.   
  5.     /* sanity check */  
  6.     if (interface_ready() == FALSE)  
  7.         return BT_STATUS_NOT_READY;  
  8.   
  9.     return btif_enable_bluetooth();  
  10. }  
又是一个新函数,直接跳转,比起刚才的寻觅这太幸福了
[cpp] view plaincopy
  1. bt_status_t btif_enable_bluetooth(void)  
  2. {  
  3.     BTIF_TRACE_DEBUG0("BTIF ENABLE BLUETOOTH");  
  4.   
  5.     if (btif_core_state != BTIF_CORE_STATE_DISABLED)  
  6.     {  
  7.         ALOGD("not disabled\n");  
  8.         return BT_STATUS_DONE;  
  9.     }  
  10.   
  11.     btif_core_state = BTIF_CORE_STATE_ENABLING;  
  12.   
  13.     /* Create the GKI tasks and run them */  
  14.     bte_main_enable(btif_local_bd_addr.address);  
  15.   
  16.     return BT_STATUS_SUCCESS;  
  17. }  
忘了写路径了 好在可以直接跳转,下面是/external/bluetooth/bluedroid/main/bte_main.c,有点长,暂时只关心set_power那部分就好了,
[cpp] view plaincopy
  1. void bte_main_enable(uint8_t *local_addr)  
  2. {  
  3.     APPL_TRACE_DEBUG1("%s", __FUNCTION__);  
  4.     ........................  
  5.   
  6. #if (defined (BT_CLEAN_TURN_ON_DISABLED) && BT_CLEAN_TURN_ON_DISABLED == TRUE)  
  7.         APPL_TRACE_DEBUG1("%s  Not Turninig Off the BT before Turninig ON", __FUNCTION__);  
  8.   
  9. #else  
  10.         /* toggle chip power to ensure we will reset chip in case 
  11.            a previous stack shutdown wasn't completed gracefully */  
  12.         bt_hc_if->set_power(BT_HC_CHIP_PWR_OFF);  
  13. #endif  
  14.         bt_hc_if->set_power(BT_HC_CHIP_PWR_ON);  
  15.   
  16.         bt_hc_if->preload(NULL);  
  17.     }  
  18.   
  19.      .............................  
  20. }  

路径在这里/external/bluetooth/bluedroid/hci/src/bt_hci_bdroid.c,看看set_power里面有什么,快到头了

[cpp] view plaincopy
  1. static void set_power(bt_hc_chip_power_state_t state)  
  2. {  
  3.     int pwr_state;  
  4.   
  5.     BTHCDBG("set_power %d", state);  
  6.   
  7.     /* Calling vendor-specific part */  
  8.     pwr_state = (state == BT_HC_CHIP_PWR_ON) ? BT_VND_PWR_ON : BT_VND_PWR_OFF;  
  9.   
  10.     if (bt_vnd_if)  
  11.         bt_vnd_if->op(BT_VND_OP_POWER_CTRL, &pwr_state);  
  12.     else  
  13.         ALOGE("vendor lib is missing!");  
  14. }  
        这下又有新东西了bt_vnd_if,这个是什么,bt_vendor_interface_t *bt_vnd_if=NULL;和刚才的bt_interface_t 一样,我们希望可以找到它的初始化,那样就可以继续跟踪了,不过看到下面的代码和注释,在源码中我们要绝望了。路径:/external/bluetooth/bluedroid/hci/include/bt_vendor_lib.h

[cpp] view plaincopy
  1. /* Entry point of DLib -- 
  2.  *      Vendor library needs to implement the body of bt_vendor_interface_t 
  3.  *      structure and uses the below name as the variable name. HCI library 
  4.  *      will use this symbol name to get address of the object through the 
  5.  *      dlsym call. 
  6.  */  
  7. extern const bt_vendor_interface_t BLUETOOTH_VENDOR_LIB_INTERFACE;  
  8.   
  9. bt_vendor_interface_t *bt_vnd_if=NULL;  

 google定义好了接口,具体实现要看vendor厂商来做了,这后面怎么实现就看各家芯片商怎么写了,肯定各有不同,而且这一部分代码一般是不会公开,当然授权购买后除外了。所以在4.2的源码中我们只跟到这里了,那后面会做什么呢,加载驱动和上电这两项肯定要有了,打开蓝牙没这两步怎么行,类似下面的字符串

[cpp] view plaincopy
  1. static const char* BT_DRIVER_MODULE_PATH =    "/system/lib/modules/mbt8xxx.ko";  
  2. static const char* BT_DRIVER_MODULE_NAME =     "bt8xxx";  
  3. static const char* BT_DRIVER_MODULE_INIT_ARG = " init_cfg=";  
  4. static const char* BT_DRIVER_MODULE_INIT_CFG_PATH = "bt_init_cfg.conf";  

在有类似下面的动作,insmod加载驱动,rfkill控制上下电,具体厂商具体做法也不同。

[cpp] view plaincopy
  1. ret = insmod(BT_DRIVER_MODULE_PATH, arg_buf);  
  2. ret = system("/system/bin/rfkill block all");  
       写到这,关于4.2源码的蓝牙打开流程就算结束了,比起4.1之前的代码感觉没有以前的直观,对于vendor那部分的代码大家只能看各自厂商的代码了,一般蓝牙开启后才会上电,这样也比较符合逻辑和节省电量,是否上电可以连上手机用adb shell看sys/class/rfkill目录下的state状态值,有些厂商会把蓝牙和wifi的上电算在一起,这个也是要注意的,小心误判。最后呢,这次文章写得有点仓促,写错的或遗漏地方希望朋友指出来,非常感谢。
 
 

android -- 蓝牙 bluetooth (三)搜索蓝牙

分类: Android的原生应用分析 3100人阅读 评论(8) 收藏 举报
bluetooth蓝牙startScanningAndroid4.2源码


        接上篇打开蓝牙继续,来一起看下蓝牙搜索的流程,触发蓝牙搜索的条件形式上有两种,一是在蓝牙设置界面开启蓝牙会直接开始搜索,另一个是先打开蓝牙开关在进入蓝牙设置界面也会触发搜索,也可能还有其它触发方式,但最后都要来到BluetoothSettngs.java的startScanning(),我们分析的起点也从这里开始,起步代码如下

[cpp] view plaincopy
  1. private void updateContent(int bluetoothState, boolean scanState) {  
  2.     if (numberOfPairedDevices == 0) {  
  3.          preferenceScreen.removePreference(mPairedDevicesCategory);  
  4.          if (scanState == true) {  
  5.              mActivityStarted = false;  
  6.              startScanning();  
  7.         } else<span style="font-family: Arial, Helvetica, sans-serif;">    ........</span>  
  8. }         
  9. private void startScanning() {  
  10.         if (!mAvailableDevicesCategoryIsPresent) {  
  11.             getPreferenceScreen().addPreference(mAvailableDevicesCategory);  
  12.         }  
  13.         mLocalAdapter.startScanning(true);  
  14.     }  

        其实在这里蓝牙搜索和打开流程是结构上是一致的,利用LocalBluetoothAdapter.java过渡到BluetoothAdapter.java再跳转至AdapterService.java要稍微留意下的是在这个过渡中startScaning()方法变成了startDiscovery()方法,看下代码:packages/apps/Settings/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java

[java] view plaincopy
  1. void startScanning(boolean force) {  
  2. if (!mAdapter.isDiscovering()) {  
  3.      if (!force) {  
  4.          // Don't scan more than frequently than SCAN_EXPIRATION_MS,  
  5.          // unless forced  
  6.          if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {  
  7.              return;  
  8.          }  
  9.          // If we are playing music, don't scan unless forced.  
  10.          A2dpProfile a2dp = mProfileManager.getA2dpProfile();  
  11.          if (a2dp != null && a2dp.isA2dpPlaying()) {  
  12.              return;  
  13.          }  
  14.      }  
  15. //这里才是我们最关注的,前面限制条件关注一下就行了  
  16.      if (mAdapter.startDiscovery()) {  
  17.          mLastScan = System.currentTimeMillis();  
  18.      }  
  19. }  
       BluetoothAdapter.java的那一段,路径 /frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java

[java] view plaincopy
  1. public boolean startDiscovery() {  
  2.     .............................  
  3.     AdapterService service = getService();  
  4.     if (service == nullreturn false;  
  5.     return service.startDiscovery();  
  6. }  
      这个service代码写得很明白AdapterService,转了一圈从framework又回到packages了,

      下面的代码路径自然是 :packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java,

[java] view plaincopy
  1. boolean startDiscovery() {  
  2.     enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,  
  3.             "Need BLUETOOTH ADMIN permission");  
  4.   
  5.     return startDiscoveryNative();  
  6. }  

      和打开蓝牙根本就是一个套路,上面的流程略过一小步,很简单的不写了,下面要怎么走,估计大家也都猜到了,JNI应该出场了,

       路径:/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp

[cpp] view plaincopy
  1. static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {                                                     
  2.     ALOGV("%s:",__FUNCTION__);                                  
  3.                                                                 
  4.     jboolean result = JNI_FALSE;                                
  5.     if (!sBluetoothInterface) return result;                    
  6.                                                                 
  7.     int ret = sBluetoothInterface->start_discovery();           
  8.     result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
  9.     return result;                                              
  10. }   

      在下面要去哪?稍微要动下脑筋,不过我们在上一篇android -- 蓝牙 bluetooth (二) 打开蓝牙已经说过怎么找了,注意android.mk文件,先找头文件,再找对应的实现C文件代码。就是现在回顾下,蓝牙打开和搜索的代码流程我们都看了,跳转都是一个套路,settings界面发起,LocalBluetoothAdapter.java过渡,去framework的转转(BluetoothAdapter.java)后回到packages的AdapterService.java,再走JNI来的external。流程就是这样的,相信类似的功能跳转(比如蓝牙配对,关闭蓝牙,停止扫描这些)大家都应该熟悉了,后面再有类似的功能就写函数名一笔带过了,还有这里要注意的就是这个start_discovery()实现代码的寻找,留意mk文件就是了,不复杂。小结结束,继续看代码    路径:/external/bluetooth/bluedroid/btif/src/bluetooth.c

[cpp] view plaincopy
  1. static int start_discovery(void)  
  2. {  
  3.     /* sanity check */  
  4.     if (interface_ready() == FALSE)  
  5.         return BT_STATUS_NOT_READY;  
  6.   
  7.     return btif_dm_start_discovery();  
  8. }     

        下面代码直接跳转就可以找到,路径external/bluetooth/bluedroid/btif/src/btif_dm.c  

        这个代码有点多,不过里面的信息也很多,所以连注释也一起保留的贴出来了,蓝牙的搜索实现并没有像蓝牙打开那样交由vendor厂商实现,在这里已经写出来了,仔细看下那些#if和#else,都是一些查询条件的调置,#if (BLE_INCLUDED == TRUE)   这个应该就google为蓝牙4.0 LE作的准备了,也算是今年google I/O大会上宣布即将支持蓝牙4.0低能耗版一个佐证吧,对于代码里面那些字符串的含义看这里好了external/bluetooth/bluedroid/bta/include/bta_api.h,一个头文件,大部分字符串和结构体的定义都在这了,多少还有些注释。

[cpp] view plaincopy
  1. bt_status_t btif_dm_start_discovery(void)                                    
  2. {                                                                            
  3.     tBTA_DM_INQ inq_params;                                                  
  4.     tBTA_SERVICE_MASK services = 0;                                          
  5.                                                                              
  6.     BTIF_TRACE_EVENT1("%s", __FUNCTION__);                                   
  7.     /* TODO: Do we need to handle multiple inquiries at the same time? */    
  8.                                                                              
  9.     /* Set inquiry params and call API */                                    
  10. #if (BLE_INCLUDED == TRUE)                                                   
  11.     inq_params.mode = BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;        
  12. #else                                                                        
  13.     inq_params.mode = BTA_DM_GENERAL_INQUIRY;                                
  14. #endif                                                                       
  15.     inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;                  
  16.                                                                              
  17.     inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;                  
  18.     inq_params.report_dup = TRUE;                                            
  19.                                                                              
  20.     inq_params.filter_type = BTA_DM_INQ_CLR;                                 
  21.     /* TODO: Filter device by BDA needs to be implemented here */            
  22.                                                                              
  23.     /* Will be enabled to TRUE once inquiry busy level has been received */  
  24.     btif_dm_inquiry_in_progress = FALSE;                                     
  25.     /* find nearby devices */                                                
  26.     BTA_DmSearch(&inq_params, services, bte_search_devices_evt);             
  27.                                                                              
  28.     return BT_STATUS_SUCCESS;                                                
  29. }   

      BTA_DmSearch()方法是看起来是要搜索了,不过里面这个家伙bte_search_devices_evt才是真正干活的主力,所以我们先看它,在这个函数里
[cpp] view plaincopy
  1. static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)                                                                      {                                                                                                                    
  2.        UINT16 param_len = 0;                                                                                           
  3.                                                                                                                         
  4.        if (p_data)                                                                                                      
  5.            param_len += sizeof(tBTA_DM_SEARCH);                                                                         
  6.        /* Allocate buffer to hold the pointers (deep copy). The pointers will point to the end of the tBTA_DM_SEARCH */  
  7.        switch (event)                                                                                                   
  8.        {                                                                                                                
  9.            case BTA_DM_INQ_RES_EVT:                                                                                     
  10.            {                                                                                                            
  11.                if (p_data->inq_res.p_eir)                                                                               
  12.                    param_len += HCI_EXT_INQ_RESPONSE_LEN;                                                               
  13.            }                                                                                                            
  14.            break;                                                                                                              
  15.           ..............................                                                                                                
  16.        }                                                                                                                
  17.        BTIF_TRACE_DEBUG3("%s event=%s param_len=%d", __FUNCTION__, dump_dm_search_event(event), param_len);             
  18.                                                                                                                         
  19.        /* if remote name is available in EIR, set teh flag so that stack doesnt trigger RNR */                          
  20.        if (event == BTA_DM_INQ_RES_EVT)                                                                                 
  21.            p_data->inq_res.remt_name_not_required = check_eir_remote_name(p_data, NULL, NULL);                          
  22.                                                                                                                         
  23.        btif_transfer_context (btif_dm_search_devices_evt , (UINT16) event, (void *)p_data, param_len,                   
  24.            (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);                                       
  25.    }     
         在上面的这个函数里又有这个bte_search_devices_evt,在它里我们能看一个 HAL_CBACK,这是要往回发消息了,看下这个函数的全貌,说是全貌,不过还是只贴出一个case分支,太长了,大家还是自行还源码吧。到这里已经可以知道扫描到蓝牙设备的mac地址和设备名,那个bdcpy函数就是在解析mac地址,有了这些,蓝牙搜索是到应该在界面展示成果的时候了,开始回调,忘记代码路径了,这个函数都在这个文件里:  /external/bluetooth/bluedroid/btif/src/btif_dm.c
[cpp] view plaincopy
  1. static void btif_dm_search_devices_evt (UINT16 event, char *p_param)  
  2.   
  3.    tBTA_DM_SEARCH *p_search_data;  
  4.    BTIF_TRACE_EVENT2("%s event=%s", __FUNCTION__, dump_dm_search_event(event));  
  5.   
  6.    switch (event)  
  7.    {  
  8.        case BTA_DM_DISC_RES_EVT:  
  9.        {  
  10.            p_search_data = (tBTA_DM_SEARCH *)p_param;  
  11.            /* Remote name update */  
  12.            if (strlen((const char *) p_search_data->disc_res.bd_name))  
  13.            {  
  14.                bt_property_t properties[1];  
  15.                bt_bdaddr_t bdaddr;  
  16.                bt_status_t status;  
  17.   
  18.                properties[0].type = BT_PROPERTY_BDNAME;  
  19.                properties[0].val = p_search_data->disc_res.bd_name;  
  20.                properties[0].len = strlen((char *)p_search_data->disc_res.bd_name);  
  21.                bdcpy(bdaddr.address, p_search_data->disc_res.bd_addr);  
  22.   
  23.                status = btif_storage_set_remote_device_property(&bdaddr, &properties[0]);  
  24.                ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device property", status);  
  25.                HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,  
  26.                                 status, &bdaddr, 1, properties);  
  27.            }  
  28.            /* TODO: Services? */  
  29.        }  
  30.        break;  
       一小段log,下面的文字就在上面的函数里打出来的,即便上面的写的函数没有,肯定也在附近了。
05-30 13:52:14.890  1578  2612 D bt-btif : bte_search_devices_evt event=BTA_DM_INQ_RES_EVT param_len=524
05-30 13:52:14.890  1578  2612 D bt-btif : search_devices_copy_cb: event=BTA_DM_INQ_RES_EVT
05-30 13:52:14.890  1578  2584 I bt-btif : btif_dm_search_devices_evt event=BTA_DM_INQ_RES_EVT
05-30 13:52:14.890  1578  2584 D bt-btif : btif_dm_search_devices_evt() ec:89:f5:ba:fb:03 device_type = 0x1

        当然回过头我们还要看下那个BTA_DmSearch(),看它的实现,更应该是起消息发送的作用,代码在/external/bluetooth/bluedroid/bta/dm/bta_dm_api.c,这个函数具体流程并没有看多少,当工具方法看了,有时间看看还是没坏处的。
[cpp] view plaincopy
  1. void BTA_DmSearch(tBTA_DM_INQ *p_dm_inq, tBTA_SERVICE_MASK services, tBTA_DM_SEARCH_CBACK *p_cback)  
  2. {  tBTA_DM_API_SEARCH    *p_msg;  
  3.     if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) != NULL)  
  4.     {  
  5.         memset(p_msg, 0, sizeof(tBTA_DM_API_SEARCH));  
  6.   
  7.         p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;  
  8.         memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));  
  9.         p_msg->services = services;  
  10.         p_msg->p_cback = p_cback;  
  11.         p_msg->rs_res  = BTA_DM_RS_NONE;  
  12.         bta_sys_sendmsg(p_msg);  
  13.     }  
  14. }  
      看了上面方法后我们 要回去了看看,代码通过JNI下来的,回去也是看JNI的回调方法
[cpp] view plaincopy
  1. method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback""([B)V");  
deviceFoundCallback方法最后会来java层的/packages/apps/Bluetooth/src/com/android/bluetooth/btservice/RemoteDevices.java
[java] view plaincopy
  1. void deviceFoundCallback(byte[] address) {  
  2.        // The device properties are already registered - we can send the intent  
  3.        // now  
  4.        BluetoothDevice device = getDevice(address);  
  5.        debugLog("deviceFoundCallback: Remote Address is:" + device);  
  6.        DeviceProperties deviceProp = getDeviceProperties(device);  
  7.        if (deviceProp == null) {  
  8.            errorLog("Device Properties is null for Device:" + device);  
  9.            return;  
  10.        }  
  11.   
  12.        Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);  
  13.        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);  
  14.        intent.putExtra(BluetoothDevice.EXTRA_CLASS,  
  15.                new BluetoothClass(Integer.valueOf(deviceProp.mBluetoothClass)));  
  16.        intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);  
  17.        intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);  
  18.   
  19.        mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);  
  20.    }  
        到这里就是给界面发广播,应用层收到广播显示出来,通过这个handle,这个handle可以在BluetoothEventManager.java的构造函数里找到,
[java] view plaincopy
  1. addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());  
  2.     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {  
  3.     @Override  
  4.     public void onReceive(Context context, Intent intent) {  
  5.         String action = intent.getAction();  
  6.         BluetoothDevice device = intent  
  7.                 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);  
  8.   
  9.         Handler handler = mHandlerMap.get(action);  
  10.         if (handler != null) {  
  11.             handler.onReceive(context, intent, device);  
  12.         }  
  13.     }  
  14. };  
         这里handle对应要看DeviceFoundHandler,也就是下面贴出来的代码,
[java] view plaincopy
  1.  private class DeviceFoundHandler implements Handler {  
  2.     public void onReceive(Context context, Intent intent,  
  3.             BluetoothDevice device) {  
  4.        ........................  
  5.         // TODO Pick up UUID. They should be available for 2.1 devices.  
  6.         // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.  
  7.         CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);  
  8.         if (cachedDevice == null) {  
  9.             cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);  
  10.             Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "  
  11.                     + cachedDevice);  
  12.             // callback to UI to create Preference for new device  
  13.             dispatchDeviceAdded(cachedDevice);  
  14.         }  
  15.       ......................  
  16.     }  
  17. }  
        在if语句中dispatchDeviceAdded()向界面分发消息,最后处理消息的地方在这里,已经到settings应用里了
   /packages/apps/Settings/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
[java] view plaincopy
  1. public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {                   
  2.    if (mDevicePreferenceMap.get(cachedDevice) != null) {                          
  3.        return;                                                                    
  4.    }                                                                              
  5.                                                                                   
  6.    // Prevent updates while the list shows one of the state messages              
  7.    if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;    
  8.                                                                                   
  9.    if (mFilter.matches(cachedDevice.getDevice())) {                               
  10.        createDevicePreference(cachedDevice);                                      
  11.    }                                                                              
  12. }                     
         上面代码中最后一个分支就是界面显示要做的事了,从settings界面开始再到settings界面显示出搜索到蓝牙结束,后面的代码不再写了,本文关心的东东到此结束。
[java] view plaincopy
  1.   void createDevicePreference(CachedBluetoothDevice cachedDevice) {  
  2.     BluetoothDevicePreference preference = new BluetoothDevicePreference(  
  3.             getActivity(), cachedDevice);  
  4.   
  5.     initDevicePreference(preference);  
  6.     mDeviceListGroup.addPreference(preference);  
  7.     mDevicePreferenceMap.put(cachedDevice, preference);  
  8. }  

        到目前为止,包括前面的打开流程分析,还仅是针对代码流程做的分析,对于蓝牙协议方面东西还没有涉及,比如蓝牙是如何发现其它蓝牙设备,这个流程究竟是怎么工作还不是很清楚,后续会尽量关注这些问题,估计看起来就没那么容易,欢迎有经验的朋友指点一二,当然对于本文不足,欢迎拍砖讨论。分享是快乐的,谢谢!


--------------modify 2013.6.2 21:08--------

更新蓝牙搜索返回后的跳转代码
 
 
 

android -- 蓝牙 bluetooth (四)OPP文件传输

分类: Android的原生应用分析 3865人阅读 评论(21) 收藏 举报
4.2源码ANDROIDBTopp蓝牙

        在前面android -- 蓝牙 bluetooth (一) 入门文章结尾中提到了会按四个方面来写这系列的文章,前面已写了蓝牙打开和蓝牙搜索,这次一起来看下蓝牙文件分享的流程,也就是蓝牙应用opp目录下的代码,作为蓝牙最基本的一个功能,这部分的代码在之前的版本中就已经有了,新旧版本代码对比很多类名都是一样的,这一部分新东西不多,写在这里帮助大家梳理下流程吧。

        有没有这种感觉,智能手机的普及让我们提高了一点对蓝牙的关注,手机间使用蓝牙互传文件应该是最常用的应用之一,手机与电脑也可以通过蓝牙做同样的事情,大部分笔记本都支持蓝牙功能,本本上蓝牙芯片多数是broadcom的,也有其它厂商(比如东芝)不过数量不多,毕竟broadcom在BT这方面是老大。不过本本上蓝牙一般只支持蓝牙耳机听歌,并没实现对opp的支持,如果体验下手机与电脑的蓝牙文件传输怎么办呢,安装一个叫bluesoleil(中文名好像是千月)软件就可以了,这个软件对蓝牙功能的支持还是比较全的。可能需要卸载本本自带蓝牙驱动。扯淡结束,本文还是要关注手机间蓝牙opp的代码流程,这段的废话也许能帮助你提高下对蓝牙的体验。

        蓝牙发送文件时发送端先来到这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java,一个没有界面只是提取下文件信息的中转站,源码的注释写的很清楚了,两个分支action.equals(Intent.ACTION_SEND)和action.equals(Intent.ACTION_SEND_MULTIPLE)

[java] view plaincopy
  1.  if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {  
  2.             //Check if Bluetooth is available in the beginning instead of at the end  
  3.             if (!isBluetoothAllowed()) {  
  4.                 Intent in = new Intent(this, BluetoothOppBtErrorActivity.class);  
  5.                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  6.                 in.putExtra("title"this.getString(R.string.airplane_error_title));  
  7.                 in.putExtra("content"this.getString(R.string.airplane_error_msg));  
  8.                 startActivity(in);  
  9.                 finish();  
  10.                 return;  
  11.             }  
  12.             if (action.equals(Intent.ACTION_SEND)) {  
  13.                .......   
  14.                Thread t = new Thread(new Runnable() {  
  15.                             public void run() {  
  16.                                 BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)  
  17.                                     .saveSendingFileInfo(type,fileUri.toString(), false);  
  18.                                 //Done getting file info..Launch device picker  
  19.                                 //and finish this activity  
  20.                                 launchDevicePicker();  
  21.                                 finish();  
  22.                             }  
  23.                         });  ......           
  24.             } else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {  
  25.               .......                     
  26.             }  

           最前面那个isBluetoothAllowed()会判断是否处于飞行模式,如果是会禁止发送的。在launchDevicePicker()里还会判断蓝牙是否已经打开,就是下面这个条件语句(!BluetoothOppManager.getInstance(this).isEnabled())。如果已经打开了蓝牙,如果蓝牙打开了就进入设备选择界面DeviceListPreferenceFragment(DevicePickerFragment)选择设备,这个跳转过程简单说明下,注意这个new Intent(BluetoothDevicePicker.ACTION_LAUNCH)里字符串,完整定义public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH";路径frameworks/base/core/java/android/bluetooth/BluetoothDevicePicker.java,你会在setting应用的manifest.xml里发现

[html] view plaincopy
  1. <activity android:name=".bluetooth.DevicePickerActivity"  
  2.                 android:theme="@android:style/Theme.Holo.DialogWhenLarge"  
  3.                 android:label="@string/device_picker"  
  4.                 android:clearTaskOnLaunch="true">  
  5.             <intent-filter>  
  6.                 <action android:name="android.bluetooth.devicepicker.action.LAUNCH" />  
  7.                 <category android:name="android.intent.category.DEFAULT" />  
  8.             </intent-filter>  
  9.         </activity>  
          这样目标就指向了DevicePickerActivity,注意此时它的代码路径是packages/apps/Settings/src/com/android/settings/bluetooth/DevicePickerActivity.java,这个类代码很简单,只有一个onCreate并只在里加载了一个布局文件bluetooth_device_picker.xml,就是这个布局文件指明下一站在哪,看下面就知道怎么来到DevicePickerFragment了

[html] view plaincopy
  1. <fragment  
  2.         android:id="@+id/bluetooth_device_picker_fragment"  
  3.         android:name="com.android.settings.bluetooth.DevicePickerFragment"  
  4.         android:layout_width="match_parent"  
  5.         android:layout_height="0dip"  
  6.         android:layout_weight="1" />  
          到了这里,已经可看到配对过的蓝牙列表了,选择其中一个点击会来到这里,里面那个sendDevicePickedIntent是我们关心的,又发了一个广播,去找谁收了广播就好了

[java] view plaincopy
  1.     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {  
  2.         mLocalAdapter.stopScanning();  
  3.         LocalBluetoothPreferences.persistSelectedDeviceInPicker(  
  4.                 getActivity(), mSelectedDevice.getAddress());  
  5.         if ((btPreference.getCachedDevice().getBondState() ==  
  6.                 BluetoothDevice.BOND_BONDED) || !mNeedAuth) {  
  7.             sendDevicePickedIntent(mSelectedDevice);  
  8.             finish();  
  9.         } else {  
  10.             super.onDevicePreferenceClick(btPreference);  
  11.         }  
  12.     }<div>    public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH";  
  13.          private void sendDevicePickedIntent(BluetoothDevice device) {  
  14.          Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);  
  15.          intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);  
  16.          if (mLaunchPackage != null && mLaunchClass != null) {  
  17.              intent.setClassName(mLaunchPackage, mLaunchClass);  
  18.          }  
  19.         getActivity().sendBroadcast(intent);}  
  20. </div>  
        通过BluetoothDevicePicker.ACTION_DEVICE_SELECTED查找,会在/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppReceiver.java这个找到对该广播的处理,也就是下面的代码:

[java] view plaincopy
  1. else if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {  
  2.           BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);           
  3.           BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);   
  4.            
  5.           // Insert transfer session record to database  
  6.           mOppManager.startTransfer(remoteDevice);  
  7.            
  8.           // Display toast message  
  9.           String deviceName = mOppManager.getDeviceName(remoteDevice);  
  10.           .......  
  11. }  
        看来关键代码是mOppManager.startTransfer(remoteDevice),在packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppManager.java,里面开启线程执行发送动作,既然是开启线程,直接去看run方法就是了,方法里面依旧区分单个和多个文件的发送,看一个就可以。

[java] view plaincopy
  1.    public void startTransfer(BluetoothDevice device) {  
  2.         if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);  
  3.         InsertShareInfoThread insertThread;  
  4.         synchronized (BluetoothOppManager.this) {  
  5.             if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {  
  6.                 ...........  
  7.                 return;  
  8.             }  
  9.             insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,  
  10.                     mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,  
  11.                     mIsHandoverInitiated);  
  12.             if (mMultipleFlag) {  
  13.                 mfileNumInBatch = mUrisOfSendingFiles.size();  
  14.             }  
  15.         }  
  16.         insertThread.start();  
  17.     }             
  18.     public void run() {  
  19.             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
  20.             ..........  
  21.             if (mIsMultiple) {  
  22.                 insertMultipleShare();  
  23.             } else {  
  24.                 insertSingleShare();  
  25.             }  
  26.             .......... }  

       以insertSingleShare() 为例,在它的实现会看到mContext.getContentResolver().insert,不多想了,要去provider里找到insert()函数了,

对应的代码在BluetoothOppProvider.java (bluetooth\src\com\android\bluetooth\opp),insert的函数实现如下,里面又拉起BluetoothOppService,开始还以为只是针对数据库的操作,差点错过了风景。路径/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppService.java

[java] view plaincopy
  1. public Uri insert(Uri uri, ContentValues values) {  
  2.  if (rowID != -1) {  
  3.      context.startService(new Intent(context, BluetoothOppService.class));  
  4.       ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);  
  5.      context.getContentResolver().notifyChange(uri, null);  
  6.  } else {  
  7.       if (D) Log.d(TAG, "couldn't insert into btopp database");  
  8.  }  

       在BluetoothOppService的onStartCommand方法中会看到updateFromProvider(),这里又开启了一个线程UpdateThread,后续代码当然是看它的run方法了,这里面内容不少,好在这部分代码注释比较多,理解起来不难。先暂时只关心发送的动作insertShare方法,代码也不少,只贴出了告诉我们接下来去哪里的代码和有关的逻辑注释,在下面的代码我们可以看到 BluetoothOppTransfer.java的对象,下一站就是它了。

[java] view plaincopy
  1. private void insertShare(Cursor cursor, int arrayPos) {  
  2.     .........  
  3.     /* 
  4.      * Add info into a batch. The logic is 
  5.      * 1) Only add valid and readyToStart info 
  6.      * 2) If there is no batch, create a batch and insert this transfer into batch, 
  7.      * then run the batch 
  8.      * 3) If there is existing batch and timestamp match, insert transfer into batch 
  9.      * 4) If there is existing batch and timestamp does not match, create a new batch and 
  10.      * put in queue 
  11.      */  
  12.     if (info.isReadyToStart()) {  
  13.         .............  
  14.         if (mBatchs.size() == 0) {  
  15.            ........  
  16.             mBatchs.add(newBatch);  
  17.             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
  18.                  mTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch);  
  19.             } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {  
  20.                 mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch,  
  21.                         mServerSession);  
  22.             }  
  23.   
  24.             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {  
  25.                 mTransfer.start();  
  26.             } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND  
  27.                     && mServerTransfer != null) {  
  28.                 mServerTransfer.start();  
  29.             }  
  30.   
  31.         } else {  
  32.             .........  
  33.     }}  
        虽然名字是start(),可实际并不是什么线程的,就是一普通方法的,路径是/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppTransfer.java

[java] view plaincopy
  1. public void start() {  
  2.       ....这里省略未贴的代码是检查蓝牙是否打开,一个很谨慎的判断。看似无用,不过还是安全第一。  
  3.   
  4.       if (mHandlerThread == null) {  
  5.           ........  
  6.           if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
  7.               /* for outbound transfer, we do connect first */  
  8.               startConnectSession();  
  9.           } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {  
  10.               /* 
  11.                * for inbound transfer, it's already connected, so we start 
  12.                * OBEX session directly 
  13.                */  
  14.               startObexSession();  
  15.           }  
  16.       }  
  17.   }  

        上面的代码是分发送文件和接收文件的,看下这两行代码就很清楚了,如果分享给别人是OUTBOUND,先执行startConnectSession(),这个函数最后还是要跑到startObexSession()这里的,如果收文件直接startObexSession,所以后面就只看startObexSession方法了

[java] view plaincopy
  1. // This transfer is outbound, e.g. share file to other device.  
  2.  public static final int DIRECTION_OUTBOUND = 0;  
  3.  // This transfer is inbound, e.g. receive file from other device.  
  4.  public static final int DIRECTION_INBOUND = 1;  
       还是在同一个类里,发送流程快结束了,同样区分是传入还是传出,发文件看OUTBOUND,去BluetoothOppObexClientSession.java

[java] view plaincopy
  1. private void startObexSession() {       
  2.       if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
  3.           if (V) Log.v(TAG, "Create Client session with transport " + mTransport.toString());  
  4.           mSession = new BluetoothOppObexClientSession(mContext, mTransport);  
  5.       } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {           
  6.           if (mSession == null) {  
  7.                markBatchFailed();  
  8.               mBatch.mStatus = Constants.BATCH_STATUS_FAILED;  
  9.               return;  
  10.           }  
  11.           if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());  
  12.       }  
  13.       mSession.start(mSessionHandler);  
  14.       processCurrentShare();  
  15.   }  
       同样名字是start,实际只是一个普通方法而已,会看又是一个线程 mThread = new ClientThread(mContext, mTransport),这时的start才是线程的start(),还是看run方法,一些线程状态的判断,看到doSend() 就是了,直正的发送在这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java,

[java] view plaincopy
  1. private void doSend() {  
  2.   
  3.      int status = BluetoothShare.STATUS_SUCCESS;  
  4.      ........关于status值的判断  
  5.      if (status == BluetoothShare.STATUS_SUCCESS) {  
  6.          /* do real send */ //看到这个注释了没,它才是真家伙sendFile  
  7.          if (mFileInfo.mFileName != null) {  
  8.              status = sendFile(mFileInfo);  
  9.          } else {  
  10.              /* this is invalid request */  
  11.              status = mFileInfo.mStatus;  
  12.          }  
  13.          waitingForShare = true;  
  14.      } else {  
  15.          Constants.updateShareStatus(mContext1, mInfo.mId, status);  
  16.      }  
  17.   
  18.      if (status == BluetoothShare.STATUS_SUCCESS) {  
  19.          Message msg = Message.obtain(mCallback);  
  20.          msg.what = BluetoothOppObexSession.MSG_SHARE_COMPLETE;  
  21.          msg.obj = mInfo;  
  22.          msg.sendToTarget();  
  23.      } else {  
  24.          Message msg = Message.obtain(mCallback);  
  25.          msg.what = BluetoothOppObexSession.MSG_SESSION_ERROR;  
  26.          mInfo.mStatus = status;  
  27.          msg.obj = mInfo;  
  28.          msg.sendToTarget();  
  29.      }  
  30.  }  
        sendFile是真正干活的,执行完sendFile会把分享成功或失败的消息传回去,sendFile里会执行打包的过程,对于字段的含义要看Headset.java,

代码路径在frameworks/base/obex/javax/obex/HeaderSet.java。这个sendFile方法行数虽然多,不过逻辑还是比较清晰的,在这里就不贴了。到这蓝牙发送文件流程也就此结束。由于发送文件时长肯定是不确定,所以在这个流程我们看到了很多开启线程代码也是很正常的,对于这线程,直接看对应的run方法就是了。

        对于蓝牙接收文件时会收到MSG_INCOMING_BTOPP_CONNECTION消息,收到这个消息是由于在蓝牙打开,即蓝牙状态是 BluetoothAdapter.STATE_ON时会执行

startSocketListener(),在这个函数开启了监听程序,看下面贴在一起的代码就明白了,

[java] view plaincopy
  1. if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {  
  2.     switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {  
  3.         case BluetoothAdapter.STATE_ON:  
  4.             if (V) Log.v(TAG,"Receiver BLUETOOTH_STATE_CHANGED_ACTION, BLUETOOTH_STATE_ON");  
  5.             startSocketListener();  
  6.             break;  
  7.                           
  8. private void startSocketListener() {  
  9.         if (V) Log.v(TAG, "start RfcommListener");  
  10.         mSocketListener.start(mHandler);  
  11.         if (V) Log.v(TAG, "RfcommListener started");  
  12. }  
  13. mSocketListener.start(mHandler);这个的实现在这里,比较长,没有贴上来     
  14. /packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppRfcommListener.java  
        回到上面处理消息,在BluetoothOppService.java的handlemessage中这个分支 case BluetoothOppRfcommListener.MSG_INCOMING_BTOPP_CONNECTION, 创建一个 createServerSession(transport); 最后走/frameworks/base/obex/javax/obex/ServerSession.java的run方法中接收数据

[java] view plaincopy
  1. private void createServerSession(ObexTransport transport) {  
  2.      mServerSession = new BluetoothOppObexServerSession(this, transport);  
  3.      mServerSession.preStart();       
  4.  }  
        对于蓝牙接收文件部分的流程还没有细致的跟踪,暂时只看到这里,对于了解基本流程这此应该够用了,同时如果想更好理解蓝牙OPP文件传输,了解是OBEX基础协议也是有必要的,网上资料还是有不少的,多数是论文形式的。对于蓝牙OPP部分,本文只是描述android代码中的流程,旨在帮你快速的理清流程,本文对OPP本身并没有深入,相关的知识需要进一步学习才行,有同道先行的童鞋还望赐教一二,谢谢。
 
 

android -- 蓝牙 bluetooth (五)接电话与听音乐

分类: Android的原生应用分析 3399人阅读 评论(12) 收藏 举报
蓝牙androidbluetooth4.2源码HFP

目录(?)[+]

        前段时间似乎所有的事情都赶在一起,回家、集体出游、出差,折腾了近一个月,终于算暂时清静了,但清静只是暂时,估计马上又要出差了,所以赶紧把蓝牙这一部分的文章了结下,按之前提到的目录,本文是关于蓝牙接打电话和听音乐的流程分析,对应蓝牙HFP/A2DP的profile,由于这部分也算是蓝牙的经典功能,所以代码流程并不是很复杂,当然不复杂仅是对于代码调用流程而言,对于HFP/A2DP协议相关的东东还没有精力去看,其难易程序也无法评价。下面从两个点HFP与A2DP来展开本文的代码跟踪:

        正文开始之前,先说点题外话,在android系统中蓝牙耳机和听筒两者的音频通道是不一样的,使用蓝牙耳机接听电话和听音乐不仅涉及到本文下面提到的流程,更要牵扯的音频通道的切换,这是一个相对比较复杂的过程,android的音频系统相关内容可不算少,个人感觉多少了下解相关知识可能有助于我们更好的蓝牙这部分功能,不过本文的主题当然还是下面两个。

      1.蓝牙耳机接听电话

        这个就对应HFP(Hands-freeProfile),Free your Hand,蓝牙的初衷之一。先来看这个功能的场景,手机来电,手机与蓝牙耳机已连接,这时会优先触发蓝牙接听电话的代码流程,起步代码在phone\src\com\android\phone\nCallScreen.java的connectBluetoothAudio() /disconnectBluetoothAudio(),只看连接部分好了,注意下面代码里的注释,
[java] view plaincopy
  1. /* package */ void connectBluetoothAudio() {  
  2.   if (VDBG) log("connectBluetoothAudio()...");  
  3.   if (mBluetoothHeadset != null) {  
  4.       // TODO(BT) check return  
  5.       mBluetoothHeadset.connectAudio();  
  6.   }  
  7.   // Watch out: The bluetooth connection doesn't happen instantly;  
  8.   // the connectAudio() call returns instantly but does its real  
  9.   // work in another thread.  The mBluetoothConnectionPending flag  
  10.   // is just a little trickery to ensure that the onscreen UI updates  
  11.   // instantly. (See isBluetoothAudioConnectedOrPending() above.)  
  12.   mBluetoothConnectionPending = true;  
  13.   mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();  
         接下来就跳到蓝牙应用的管辖范围,代码在packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java,
[java] view plaincopy
  1. public boolean connectAudio() {  
  2.     HeadsetService service = getService();  
  3.     if (service == nullreturn false;  
  4.     return service.connectAudio();  
  5. }  
        很明显下一个目标是HeadsetService,直接看具体实现,这部分代码跳转都比较清晰,下面代码会先判断当前状态是否正确,关于HeadsetStateMachine几个状态可以参持这个/packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetStateMachine.java的最前的代码注释。
[java] view plaincopy
  1. boolean connectAudio() {  
  2.      // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission  
  3.      enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");  
  4.      if (!mStateMachine.isConnected()) {  
  5.          return false;  
  6.      }  
  7.      if (mStateMachine.isAudioOn()) {  
  8.          return false;  
  9.      }  
  10.      mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);  
  11.      return true;  
  12.  }  
       走进HeadsetStateMachine状态机,找到CONNECT_AUDIO分支,就看带Native的方法connectAudioNative(getByteAddress(mCurrentDevice));
[java] view plaincopy
  1. static jboolean connectAudioNative(JNIEnv *env, jobject object, jbyteArray address) {  
  2.     jbyte *addr;  
  3.     bt_status_t status;  
  4.   
  5.     if (!sBluetoothHfpInterface) return JNI_FALSE;  
  6.   
  7.     addr = env->GetByteArrayElements(address, NULL);  
  8.     if (!addr) {  
  9.         jniThrowIOException(env, EINVAL);  
  10.         return JNI_FALSE;  
  11.     }  
  12. //连接在这里  
  13.     if ( (status = sBluetoothHfpInterface->connect_audio((bt_bdaddr_t *)addr)) !=    
  14.          BT_STATUS_SUCCESS) {  
  15.         ALOGE("Failed HF audio connection, status: %d", status);  
  16.     }  
  17.     env->ReleaseByteArrayElements(address, addr, 0);  
  18.     return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
  19. }  
       上面代码还可以进一步跟到下面/external/bluetooth/bluedroid/btif/src/btif_hf.c,到了这里其实流程已经结束了,对于这里消息流转估计要放到以后再写了
[java] view plaincopy
  1. static bt_status_t connect_audio( bt_bdaddr_t *bd_addr )  
  2. {  
  3.     CHECK_BTHF_INIT();  
  4.     if (is_connected(bd_addr))  
  5.     {  
  6.         BTA_AgAudioOpen(btif_hf_cb.handle);  
  7.         /* Inform the application that the audio connection has been initiated successfully */  
  8.         btif_transfer_context(btif_in_hf_generic_evt, BTIF_HFP_CB_AUDIO_CONNECTING,  
  9.                               (char *)bd_addr, sizeof(bt_bdaddr_t), NULL);  
  10.         return BT_STATUS_SUCCESS;  
  11.     }  
  12.     return BT_STATUS_FAIL;  
  13. }  

 2.在蓝牙列表中连接蓝牙耳机

     A2dp的连接过程,在蓝牙搜索结果列表连接一个蓝牙耳机,既然是从设备列表开始,所以起步代码自然是这个了
[java] view plaincopy
  1. DevicePickerFragment.java (settings\src\com\android\settings\bluetooth)     3884     2013-6-26  
  2.     void onClicked() {  
  3.       int bondState = mCachedDevice.getBondState();  
  4.       if (mCachedDevice.isConnected()) {  
  5.           askDisconnect();  
  6.       } else if (bondState == BluetoothDevice.BOND_BONDED) {  
  7.           mCachedDevice.connect(true);  
  8.       } .......  
  9.   }  
  10.   
  11.     void connect(boolean connectAllProfiles) {  
  12.       if (!ensurePaired()) {  //要先确保配对  
  13.           return;  
  14.       }  
  15.       mConnectAttempted = SystemClock.elapsedRealtime();  
  16.       connectWithoutResettingTimer(connectAllProfiles);//没别的了,只能看到这里  
  17.   }  
      代码路径这里packages/apps/Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java,具体代码看下面
[java] view plaincopy
  1. // Try to initialize the profiles if they were not.  
  2.      ...........  
  3.       // Reset the only-show-one-error-dialog tracking variable  
  4.       mIsConnectingErrorPossible = true;  
  5.   
  6.       int preferredProfiles = 0;  
  7.       for (LocalBluetoothProfile profile : mProfiles) {  
  8.           if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {  
  9.               if (profile.isPreferred(mDevice)) {  
  10.                   ++preferredProfiles;  
  11.                   connectInt(profile);//连接在这里,  
  12.               }  
  13.           }  
  14.       }  
  15.      .............  
       connectInt的实现很简单,直接跳过看里面的profile.connect(mDevice),这里的profile是指A2dpProfile,所以connet()方法的具体实现在
[java] view plaincopy
  1. public boolean connect(BluetoothDevice device) {  
  2.     if (mService == nullreturn false;  
  3.     List<BluetoothDevice> sinks = getConnectedDevices();  
  4.     if (sinks != null) {  
  5.         for (BluetoothDevice sink : sinks) {  
  6.             mService.disconnect(sink);  
  7.     }}  
  8.     return mService.connect(device);  
  9. }  
        下面是 BluetoothA2dp.java (frameworks\base\core\java\android\bluetooth)  ,为什么是这样看下这个private BluetoothA2dp mService;就知道了
[java] view plaincopy
  1. public boolean connect(BluetoothDevice device) {  
  2.       if (mService != null && isEnabled() &&  
  3.           isValidDevice(device)) {  
  4.           try {  
  5.               return mService.connect(device);  
  6.           } catch (RemoteException e) {  
  7.               Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));  
  8.               return false;  
  9.           }  
  10.       }...........  
  11.       return false;  
  12.        
  13.       Binder跳转  
  14.       public boolean connect(BluetoothDevice device) {  
  15.           A2dpService service = getService();  
  16.           if (service == nullreturn false;  
  17.           return service.connect(device);  
  18.       }  
  19.        
  20.   }  
        之后的跳转和第一部分蓝牙接听电话跳转过程类似,就不重复了,最后会来到packages/apps/Bluetooth/jni/com_android_bluetooth_a2dp.cpp的connectA2dpNative,同样到下面的代码,我们能看到的开放的代码也就是这些,再下面要看vendor的具体实现了。
[java] view plaincopy
  1. static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) {  
  2.   jbyte *addr;  
  3.   bt_bdaddr_t * btAddr;  
  4.   bt_status_t status;  
  5.   
  6.   ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface);  
  7.   if (!sBluetoothA2dpInterface) return JNI_FALSE;  
  8.   
  9.   addr = env->GetByteArrayElements(address, NULL);  
  10.   btAddr = (bt_bdaddr_t *) addr;  
  11.   if (!addr) {  
  12.       jniThrowIOException(env, EINVAL);  
  13.       return JNI_FALSE;  
  14.   }  
  15.   if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {  
  16.       ALOGE("Failed HF connection, status: %d", status);  
  17.   }  
  18.   env->ReleaseByteArrayElements(address, addr, 0);  
  19.   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
       那到此为止,本文关于蓝牙耳机与蓝牙接听电话的流程分析也就结束了,同时蓝牙这一系列的文章也暂时结束,当然后续依然会关注蓝牙。本系列的第一篇文章标题是入门,现在想想,这五篇文章下来也不过是刚刚入门而已,协议部分更是没怎么涉及呢,对于蓝牙BT需要深入研究的地方还有很多,仅希望这五篇文章可以帮你快速了解android蓝牙代码流程,回顾以前四篇文章请点击链接:
       android -- 蓝牙 bluetooth (一) 入门
       android -- 蓝牙 bluetooth (二) 打开蓝牙
       android -- 蓝牙 bluetooth (三)搜索蓝牙
       android -- 蓝牙 bluetooth (四)OPP文件传输    
       最后感谢在前面文章中网友的热心回复与纠正,学习路上一起分享是快乐的。谢谢!

0 0
原创粉丝点击