Android 藍牙開發(六)hfp連接

轉載請註明出處:http://blog.csdn.net/vnanyesheshou/article/details/71106622

本文已授權微信公衆號 fanfan程序媛 獨家發佈 掃一掃文章底部的二維碼或在微信搜索 fanfan程序媛 即可關注

五一過後,接着研究Android藍牙hfp相關技術。本文主要內容是藍牙手機音頻的連接、斷開流程分析,對應藍牙HFP profile。
該文章是基於Android源碼4.3的


1 hfp簡單介紹

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

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


2 手機音頻連接

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

public boolean onPreferenceChange(Preference preference, Object newValue) {
    if (preference == mDeviceNamePref) { //重命名
        mCachedDevice.setName((String) newValue);
    } else if (preference instanceof CheckBoxPreference) {//check box
        LocalBluetoothProfile prof = getProfileOf(preference); //獲取對應的profile
        onProfileClicked(prof, (CheckBoxPreference) preference);
        return false;   // checkbox will update from onDeviceAttributesChanged() callback
    } else {
        return false;
    }
    return true;
}

接着看onProfileClicked()函數處理

private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
    BluetoothDevice device = mCachedDevice.getDevice(); //獲取配對的藍牙設備
    int status = profile.getConnectionStatus(device);  //獲取profile的連接狀態
    boolean isConnected =
            status == BluetoothProfile.STATE_CONNECTED;
    if (isConnected) { //如果是連接狀態則斷開連接
        askDisconnect(getActivity(), profile);
    } else { //沒有連接
        if (profile.isPreferred(device)) { //獲取profile是否是首選
            // profile is preferred but not connected: disable auto-connect
            profile.setPreferred(device, false); //設置對應profile的PRIORITY 爲off,防止自動連接
            refreshProfilePreference(profilePref, profile); //刷新check box狀態
        } else {
            profile.setPreferred(device, true); //設置對應profile的PRIORITY 爲on
            mCachedDevice.connectProfile(profile); //連接指定profile
        }
    }
}

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

void connectProfile(LocalBluetoothProfile profile) {
    mConnectAttempted = SystemClock.elapsedRealtime();
    // Reset the only-show-one-error-dialog tracking variable
    mIsConnectingErrorPossible = true;
    connectInt(profile); //連接profile
    refresh();    // 刷新ui
}

synchronized void connectInt(LocalBluetoothProfile profile) {
    //查看是否配對,如果沒有配對則進行配對,配對後進行連接,
    //如果配對則直接連接
    if (!ensurePaired()) { 
        return;
    }
    if (profile.connect(mDevice)) {//連接
        return;
    }
}

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

public boolean connect(BluetoothDevice device) {
    if (mService == null) return false;
    //獲取連接hfp的設備
    List<BluetoothDevice> sinks = mService.getConnectedDevices();
    if (sinks != null) {
        for (BluetoothDevice sink : sinks) {
            mService.disconnect(sink); //斷開連接
        }
    } //連接hfp。
    return mService.connect(device);
}

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

public boolean connect(BluetoothDevice device) {
    HeadsetService service = getService();
    if (service == null) return false;
    return service.connect(device);
}

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

public boolean connect(BluetoothDevice device) {
    enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                   "Need BLUETOOTH ADMIN permission");
    if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
        return false;  //檢查priority
    }
    int connectionState = mStateMachine.getConnectionState(device);
    if (connectionState == BluetoothProfile.STATE_CONNECTED ||
        connectionState == BluetoothProfile.STATE_CONNECTING) {
        return false; //檢查連接狀態
    }
    mStateMachine.sendMessage(HeadsetStateMachine.CONNECT, device);
    return true;
}

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

BluetoothDevice device = (BluetoothDevice) message.obj;
//發送廣播,正在連接hfp
broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
               BluetoothProfile.STATE_DISCONNECTED);
//連接遠端設備。
if (!connectHfpNative(getByteAddress(device)) ) {
    //連接失敗,向外發送連接失敗廣播
    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
                   BluetoothProfile.STATE_CONNECTING);
    break;
}
synchronized (HeadsetStateMachine.this) {
    mTargetDevice = device;
    transitionTo(mPending); //切換到pending狀態
}
sendMessageDelayed(CONNECT_TIMEOUT, 30000);

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

static jboolean connectHfpNative(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((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;
}

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


3 連接狀態

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

static void connection_state_callback(bthf_connection_state_t state, bt_bdaddr_t* bd_addr) {
    jbyteArray addr;
    CHECK_CALLBACK_ENV
    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (!addr) {
        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
        return;
    }
    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
                                 (jint) state, addr);
    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
    sCallbackEnv->DeleteLocalRef(addr);
}

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

private void onConnectionStateChanged(int state, byte[] address) {
    StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
    event.valueInt = state;
    event.device = getDevice(address);
    sendMessage(STACK_EVENT, event);
}

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

//發送廣播,連接成功
broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
                         BluetoothProfile.STATE_CONNECTING);
synchronized (HeadsetStateMachine.this) {
    mCurrentDevice = mTargetDevice; //mCurrentDevice表示已連接的設備
    mTargetDevice = null; //mTargetDevice表示要連接的設備
    transitionTo(mConnected); //切換到Connected狀態
}

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

private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
    /* Notifying the connection state change of the profile before sending the intent for
       connection state change, as it was causing a race condition, with the UI not being
       updated with the correct connection state. */
    mService.notifyProfileConnectionStateChanged(device, BluetoothProfile.HEADSET,newState, prevState);
    Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
    intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
    intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    mService.sendBroadcast(intent, HeadsetService.BLUETOOTH_PERM);
}

在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連接過程已經分析完了,而斷開連接到過程和連接整體相差不多,就不再細說了。

歡迎掃一掃關注我的微信公衆號,定期推送優質技術文章:

這裏寫圖片描述

發佈了129 篇原創文章 · 獲贊 270 · 訪問量 97萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章