Android藍牙開發【六】hfp鏈接

HFP (Hands-free Profile),讓藍牙設備(如藍牙耳機)能夠控制電話,如接聽、掛斷、拒接、語音撥號等,拒接、語音撥號要看藍牙耳機及電話是否支持。java

HFP定義了音頻網關(AG)和免提組件(HF)兩個角色: 
音頻網關(AG) – 該設備爲音頻(特別是手機)的輸入/輸出網關。 
免提組件(HF) – 該設備做爲音頻網關的遠程音頻輸入/輸出機制,並可提供若干遙控功能。android


 

2 手機音頻鏈接

對於手機音頻的使用,首先鏈接的藍牙設備須要支持hfp協議,而且須要與該設備進行配對,如何進行藍牙配對這裏就不細說了,能夠參照個人其餘文章。主要分析下其鏈接過程。 
對於系統自帶應用Settings中已配對的藍牙設備界面(以下圖所示), 
這裏寫圖片描述
其對應文件路徑: 
packages/apps/Settings/src/com/android/settings/bluetooth/DeviceProfilesSettings.java 
點擊手機音頻進行鏈接,調用onPreferenceChange。app

 

 
  1. public boolean onPreferenceChange(Preference preference, Object newValue) {函數

  2. if (preference == mDeviceNamePref) { //重命名ui

  3. mCachedDevice.setName((String) newValue);this

  4. } else if (preference instanceof CheckBoxPreference) {//check boxspa

  5. LocalBluetoothProfile prof = getProfileOf(preference); //獲取對應的profile代理

  6. onProfileClicked(prof, (CheckBoxPreference) preference);code

  7. return false; // checkbox will update from onDeviceAttributesChanged() callback對象

  8. } else {

  9. return false;

  10. }

  11. return true;

  12. }

接着看onProfileClicked()函數處理

 
  1. private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {

  2. BluetoothDevice device = mCachedDevice.getDevice(); //獲取配對的藍牙設備

  3. int status = profile.getConnectionStatus(device); //獲取profile的鏈接狀態

  4. boolean isConnected =

  5. status == BluetoothProfile.STATE_CONNECTED;

  6. if (isConnected) { //若是是鏈接狀態則斷開鏈接

  7. askDisconnect(getActivity(), profile);

  8. } else { //沒有鏈接

  9. if (profile.isPreferred(device)) { //獲取profile是不是首選

  10. // profile is preferred but not connected: disable auto-connect

  11. profile.setPreferred(device, false); //設置對應profile的PRIORITY 爲off,防止自動鏈接

  12. refreshProfilePreference(profilePref, profile); //刷新check box狀態

  13. } else {

  14. profile.setPreferred(device, true); //設置對應profile的PRIORITY 爲on

  15. mCachedDevice.connectProfile(profile); //鏈接指定profile

  16. }

  17. }

  18. }

接着查看CachedBluetoothDevice中的connectProfile函數鏈接某一profile。

 
  1. void connectProfile(LocalBluetoothProfile profile) {

  2. mConnectAttempted = SystemClock.elapsedRealtime();

  3. // Reset the only-show-one-error-dialog tracking variable

  4. mIsConnectingErrorPossible = true;

  5. connectInt(profile); //鏈接profile

  6. refresh(); // 刷新ui

  7. }

  8.  
  9. synchronized void connectInt(LocalBluetoothProfile profile) {

  10. //查看是否配對,若是沒有配對則進行配對,配對後進行鏈接,

  11. //若是配對則直接鏈接

  12. if (!ensurePaired()) {

  13. return;

  14. }

  15. if (profile.connect(mDevice)) {//鏈接

  16. return;

  17. }

  18. }

connectProfile() ——>connectInt() 
connectInt()函數中會先判斷是否配對,若是沒有配對則開始配對,配對成功後鏈接profile。 
若是已經配對則直接鏈接profile。 
對於profile.connect(mDevice)會根據profile調用各自對應的connect方法。(如手機音頻則對應HeadsetProfile,媒體音頻對應A2dpProfile)。這裏查看手機音頻的鏈接HeadsetProfile

 
  1. public boolean connect(BluetoothDevice device) {

  2. if (mService == null) return false;

  3. //獲取鏈接hfp的設備

  4. List<BluetoothDevice> sinks = mService.getConnectedDevices();

  5. if (sinks != null) {

  6. for (BluetoothDevice sink : sinks) {

  7. mService.disconnect(sink); //斷開鏈接

  8. }

  9. } //鏈接hfp。

  10. return mService.connect(device);

  11. }

HeadsetProfile.java中的connect()方法,mService是經過getProfileProxy獲取的BluetoothHeadset代理對象,經過其進行hfp相關操做。 
mService.connect跳到Bluetooth應用中, 
代碼路徑:packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java 
先調用到內部類BluetoothHeadsetBinder的connect方法。

 
  1. public boolean connect(BluetoothDevice device) {

  2. HeadsetService service = getService();

  3. if (service == null) return false;

  4. return service.connect(device);

  5. }

該方法中很明顯是去調用HeadsetService的connect方法。

 

 
  1. public boolean connect(BluetoothDevice device) {

  2. enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,

  3. "Need BLUETOOTH ADMIN permission");

  4. if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {

  5. return false; //檢查priority

  6. }

  7. int connectionState = mStateMachine.getConnectionState(device);

  8. if (connectionState == BluetoothProfile.STATE_CONNECTED ||

  9. connectionState == BluetoothProfile.STATE_CONNECTING) {

  10. return false; //檢查鏈接狀態

  11. }

  12. mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);

  13. return true;

  14. }


HeadsetService的connect()函數會對priority和鏈接狀態進行必要的檢查,不符合條件則返回false。符合條件則向狀態機發送消息HeadsetStateMachine.CONNECT。 
此時HeadsetStateMachine中狀態應該是Disconnected,因此查看Disconnected state中的處理

 
  1. BluetoothDevice device = (BluetoothDevice) message.obj;

  2. //發送廣播,正在鏈接hfp

  3. broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,

  4. BluetoothProfile.STATE_DISCONNECTED);

  5. //鏈接遠端設備。

  6. if (!connectHfpNative(getByteAddress(device)) ) {

  7. //鏈接失敗,向外發送鏈接失敗廣播

  8. broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,

  9. BluetoothProfile.STATE_CONNECTING);

  10. break;

  11. }

  12. synchronized (HeadsetStateMachine.this) {

  13. mTargetDevice = device;

  14. transitionTo(mPending); //切換到pending狀態

  15. }

  16. sendMessageDelayed(CONNECT_TIMEOUT, 30000);

HeadsetStateMachine調用connectHfpNative()函數來進行手機音頻的鏈接。connectHfpNative是native方法,跳轉到com_android_bluetooth_hfp.cpp中,調用對應的方法connectHfpNative

 
  1. static jboolean connectHfpNative(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. if ((status = sBluetoothHfpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {

  11. ALOGE("Failed HF connection, status: %d", status);

  12. }

  13. env->ReleaseByteArrayElements(address, addr, 0);

  14. return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

  15. }

其中sBluetoothHfpInterface->connect會跳到藍牙協議棧進行鏈接,協議棧就先不進行分析了。

 

3 鏈接狀態

當協議棧鏈接狀態改變會回調com_android_bluetooth_hfp.cpp中的方法connection_state_callback()。

 
  1. static void connection_state_callback(bthf_connection_state_t state, bt_bdaddr_t* bd_addr) {

  2. jbyteArray addr;

  3. CHECK_CALLBACK_ENV

  4. addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));

  5. if (!addr) {

  6. checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);

  7. return;

  8. }

  9. sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);

  10. sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,

  11. (jint) state, addr);

  12. checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);

  13. sCallbackEnv->DeleteLocalRef(addr);

  14. }

在connection_state_callback方法中會從cpp層調用到java層,對應於HeadsetStateMachine中的onConnectionStateChanged函數

 
  1. private void onConnectionStateChanged(int state, byte[] address) {

  2. StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);

  3. event.valueInt = state;

  4. event.device = getDevice(address);

  5. sendMessage(STACK_EVENT, event);

  6. }


onConnectionStateChanged函數中發送消息STACK_EVENT(攜帶狀態和藍牙地址),此時是Pending state,收到該消息調用processConnectionEvent。 
正常鏈接成功應該會先收到HeadsetHalConstants.CONNECTION_STATE_CONNECTING狀態,而後收到HeadsetHalConstants.CONNECTION_STATE_CONNECTED狀態。

 
  1. //發送廣播,鏈接成功

  2. broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,

  3. BluetoothProfile.STATE_CONNECTING);

  4. synchronized (HeadsetStateMachine.this) {

  5. mCurrentDevice = mTargetDevice; //mCurrentDevice表示已鏈接的設備

  6. mTargetDevice = null; //mTargetDevice表示要鏈接的設備

  7. transitionTo(mConnected); //切換到Connected狀態

  8. }


收到HeadsetHalConstants.CONNECTION_STATE_CONNECTED狀態,後向外發送鏈接成功的廣播,狀態機切換到Connected狀態

 

 
  1. private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {

  2. /* Notifying the connection state change of the profile before sending the intent for

  3. connection state change, as it was causing a race condition, with the UI not being

  4. updated with the correct connection state. */

  5. mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.HEADSET,newState, prevState);

  6. Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);

  7. intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);

  8. intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);

  9. intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);

  10. mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);

  11. }

在mService.notifyProfileConnectionStateChanged中會將手機音頻的proirty設置爲auto_connect,而且向外發送BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED廣播。

在其餘應用中能夠經過廣播接收者註冊BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED該廣播,用來監聽hfp的鏈接狀態。


 

4 更新ui

當手機音頻鏈接成功後,Settings應用中會更新ui界面。 
LocalBluetoothProfileManager中會對全部的profile進行管理,其將hfp的profile添加到BluetoothEventManager中,BluetoothEventManager會註冊藍牙狀態改變、各profile狀態改變等廣播。 
當BluetoothEventManager收到BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED廣播後,會根據action獲取對應的handler,調用對應handler的onReceive方法。 
接收到該廣播跳到LocalBluetoothProfileManager內部類StateChangedHandler.onReceive->CachedBluetoothDevice.onProfileStateChanged ->refresh ->dispatchAttributesChanged 
接着跳到DeviceProfilesSettings中的onDeviceAttributesChanged ->refresh.這裏會對界面進行更新,顯示其鏈接狀態信息。

hfp鏈接過程已經分析完了,而斷開鏈接到過程和鏈接總體相差很少,就再也不細說了。

相關文章
相關標籤/搜索