天天看點

android 4.2藍牙bluetooth 第5節:接打電話

前段時間似乎所有的事情都趕在一起,回家、集體出遊、出差,折騰了近一個月,終于算暫時清靜了,但清靜隻是暫時,估計馬上又要出差了,是以趕緊把藍牙這一部分的文章了結下,按之前提到的目錄,本文是關于藍牙接打電話和聽音樂的流程分析,對應藍牙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 plain copy print ?

  1.  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();  
/* package */ void connectBluetoothAudio() {
        if (VDBG) log("connectBluetoothAudio()...");
        if (mBluetoothHeadset != null) {
            // TODO(BT) check return
            mBluetoothHeadset.connectAudio();
        }
        // Watch out: The bluetooth connection doesn't happen instantly;
        // the connectAudio() call returns instantly but does its real
        // work in another thread.  The mBluetoothConnectionPending flag
        // is just a little trickery to ensure that the onscreen UI updates
        // instantly. (See isBluetoothAudioConnectedOrPending() above.)
        mBluetoothConnectionPending = true;
        mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
    }
           

         接下來就跳到藍牙應用的管轄範圍,代碼在packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java, [java] view plain copy print ?

  1. public boolean connectAudio() {  
  2.     HeadsetService service = getService();  
  3.     if (service == null) return false;  
  4.     return service.connectAudio();  
  5. }  
public boolean connectAudio() {
            HeadsetService service = getService();
            if (service == null) return false;
            return service.connectAudio();
        }
           

        很明顯下一個目标是HeadsetService,直接看具體實作,這部分代碼跳轉都比較清晰,下面代碼會先判斷目前狀态是否正确,關于HeadsetStateMachine幾個狀态可以參持這個/packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetStateMachine.java的最前的代碼注釋。 [java] view plain copy print ?

  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.  }  
boolean connectAudio() {
        // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        if (!mStateMachine.isConnected()) {
            return false;
        }
        if (mStateMachine.isAudioOn()) {
            return false;
        }
        mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);
        return true;
    }
           

       走進HeadsetStateMachine狀态機,找到CONNECT_AUDIO分支,就看帶Native的方法connectAudioNative(getByteAddress(mCurrentDevice)); [java] view plain copy print ?

  1. static jboolean connectAudioNative(JNIEnv *env, jobject object, jbyteArray address) {  
  2.     jbyte *addr;  
  3.     bt_status_t status;  
  4.     if (!sBluetoothHfpInterface) return JNI_FALSE;  
  5.     addr = env->GetByteArrayElements(address, NULL);  
  6.     if (!addr) {  
  7.         jniThrowIOException(env, EINVAL);  
  8.         return JNI_FALSE;  
  9.     }  
  10. //連接配接在這裡   
  11.     if ( (status = sBluetoothHfpInterface->connect_audio((bt_bdaddr_t *)addr)) !=    
  12.          BT_STATUS_SUCCESS) {  
  13.         ALOGE("Failed HF audio connection, status: %d", status);  
  14.     }  
  15.     env->ReleaseByteArrayElements(address, addr, 0);  
  16.     return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
  17. }  
static jboolean connectAudioNative(JNIEnv *env, jobject object, jbyteArray address) {
    jbyte *addr;
    bt_status_t status;

    if (!sBluetoothHfpInterface) return JNI_FALSE;

    addr = env->GetByteArrayElements(address, NULL);
    if (!addr) {
        jniThrowIOException(env, EINVAL);
        return JNI_FALSE;
    }
//連接配接在這裡
    if ( (status = sBluetoothHfpInterface->connect_audio((bt_bdaddr_t *)addr)) !=  
         BT_STATUS_SUCCESS) {
        ALOGE("Failed HF audio connection, status: %d", status);
    }
    env->ReleaseByteArrayElements(address, addr, 0);
    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
           

       上面代碼還可以進一步跟到下面/external/bluetooth/bluedroid/btif/src/btif_hf.c,到了這裡其實流程已經結束了,對于這裡消息流轉估計要放到以後再寫了 [java] view plain copy print ?

  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.         btif_transfer_context(btif_in_hf_generic_evt, BTIF_HFP_CB_AUDIO_CONNECTING,  
  8.                               (char *)bd_addr, sizeof(bt_bdaddr_t), NULL);  
  9.         return BT_STATUS_SUCCESS;  
  10.     }  
  11.     return BT_STATUS_FAIL;  
  12. }  
static bt_status_t connect_audio( bt_bdaddr_t *bd_addr )
{
    CHECK_BTHF_INIT();
    if (is_connected(bd_addr))
    {
        BTA_AgAudioOpen(btif_hf_cb.handle);
        /* Inform the application that the audio connection has been initiated successfully */
        btif_transfer_context(btif_in_hf_generic_evt, BTIF_HFP_CB_AUDIO_CONNECTING,
                              (char *)bd_addr, sizeof(bt_bdaddr_t), NULL);
        return BT_STATUS_SUCCESS;
    }
    return BT_STATUS_FAIL;
}
           

 2.在藍牙清單中連接配接藍牙耳機

     A2dp的連接配接過程,在藍牙搜尋結果清單連接配接一個藍牙耳機,既然是從裝置清單開始,是以起步代碼自然是這個了 [java] view plain copy print ?

  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.     void connect(boolean connectAllProfiles) {  
  11.       if (!ensurePaired()) {  //要先確定配對   
  12.           return;  
  13.       }  
  14.       mConnectAttempted = SystemClock.elapsedRealtime();  
  15.       connectWithoutResettingTimer(connectAllProfiles);//沒别的了,隻能看到這裡   
  16.   }  
DevicePickerFragment.java (settings\src\com\android\settings\bluetooth)     3884     2013-6-26
      void onClicked() {
        int bondState = mCachedDevice.getBondState();
        if (mCachedDevice.isConnected()) {
            askDisconnect();
        } else if (bondState == BluetoothDevice.BOND_BONDED) {
            mCachedDevice.connect(true);
        } .......
    }
 
      void connect(boolean connectAllProfiles) {
        if (!ensurePaired()) {  //要先確定配對
            return;
        }
        mConnectAttempted = SystemClock.elapsedRealtime();
        connectWithoutResettingTimer(connectAllProfiles);//沒别的了,隻能看到這裡
    }
           

      代碼路徑這裡packages/apps/Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java,具體代碼看下面

[java] view plain copy print ?

  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.       int preferredProfiles = 0;  
  6.       for (LocalBluetoothProfile profile : mProfiles) {  
  7.           if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {  
  8.               if (profile.isPreferred(mDevice)) {  
  9.                   ++preferredProfiles;  
  10.                   connectInt(profile);//連接配接在這裡,   
  11.               }  
  12.           }  
  13.       }  
  14.      .............  
// Try to initialize the profiles if they were not.
       ...........
        // Reset the only-show-one-error-dialog tracking variable
        mIsConnectingErrorPossible = true;

        int preferredProfiles = 0;
        for (LocalBluetoothProfile profile : mProfiles) {
            if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
                if (profile.isPreferred(mDevice)) {
                    ++preferredProfiles;
                    connectInt(profile);//連接配接在這裡,
                }
            }
        }
       .............
           

       connectInt的實作很簡單,直接跳過看裡面的profile.connect(mDevice),這裡的profile是指A2dpProfile,是以connet()方法的具體實作在 [java] view plain copy print ?

  1. public boolean connect(BluetoothDevice device) {  
  2.     if (mService == null) return 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. }  
public boolean connect(BluetoothDevice device) {
        if (mService == null) return false;
        List<BluetoothDevice> sinks = getConnectedDevices();
        if (sinks != null) {
            for (BluetoothDevice sink : sinks) {
                mService.disconnect(sink);
        }}
        return mService.connect(device);
    }
           

        下面是 BluetoothA2dp.java (frameworks\base\core\java\android\bluetooth)  ,為什麼是這樣看下這個private BluetoothA2dp mService;就知道了 [java] view plain copy print ?

  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.       Binder跳轉  
  13.       public boolean connect(BluetoothDevice device) {  
  14.           A2dpService service = getService();  
  15.           if (service == null) return false;  
  16.           return service.connect(device);  
  17.       }  
  18.   }  
public boolean connect(BluetoothDevice device) {
        if (mService != null && isEnabled() &&
            isValidDevice(device)) {
            try {
                return mService.connect(device);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return false;
            }
        }...........
        return false;
       
        Binder跳轉
        public boolean connect(BluetoothDevice device) {
            A2dpService service = getService();
            if (service == null) return false;
            return service.connect(device);
        }
       
    }
           

        之後的跳轉和第一部分藍牙接聽電話跳轉過程類似,就不重複了,最後會來到packages/apps/Bluetooth/jni/com_android_bluetooth_a2dp.cpp的connectA2dpNative,同樣到下面的代碼,我們能看到的開放的代碼也就是這些,再下面要看vendor的具體實作了。 [java] view plain copy print ?

  1. static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) {  
  2.   jbyte *addr;  
  3.   bt_bdaddr_t * btAddr;  
  4.   bt_status_t status;  
  5.   ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface);  
  6.   if (!sBluetoothA2dpInterface) return JNI_FALSE;  
  7.   addr = env->GetByteArrayElements(address, NULL);  
  8.   btAddr = (bt_bdaddr_t *) addr;  
  9.   if (!addr) {  
  10.       jniThrowIOException(env, EINVAL);  
  11.       return JNI_FALSE;  
  12.   }  
  13.   if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {  
  14.       ALOGE("Failed HF connection, status: %d", status);  
  15.   }  
  16.   env->ReleaseByteArrayElements(address, addr, 0);  
  17.   return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) {
    jbyte *addr;
    bt_bdaddr_t * btAddr;
    bt_status_t status;

    ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface);
    if (!sBluetoothA2dpInterface) return JNI_FALSE;

    addr = env->GetByteArrayElements(address, NULL);
    btAddr = (bt_bdaddr_t *) addr;
    if (!addr) {
        jniThrowIOException(env, EINVAL);
        return JNI_FALSE;
    }
    if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
        ALOGE("Failed HF connection, status: %d", status);
    }
    env->ReleaseByteArrayElements(address, addr, 0);
    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
           

       那到此為止,本文關于藍牙耳機與藍牙接聽電話的流程分析也就結束了,同時藍牙這一系列的文章也暫時結束,當然後續依然會關注藍牙。本系列的第一篇文章标題是 入門,現在想想,這五篇文章下來也不過是剛剛入門而已,協定部分更是沒怎麼涉及呢,對于藍牙BT需要深入研究的地方還有很多,僅希望這五篇文章可以幫你快速了解android藍牙代碼流程,回顧以前四篇文章請點選連結:         android -- 藍牙 bluetooth (一) 入門

        android -- 藍牙 bluetooth (二) 打開藍牙

        android -- 藍牙 bluetooth (三)搜尋藍牙         android -- 藍牙 bluetooth (四)OPP檔案傳輸