1. 程式人生 > >Android藍芽開發【六】hfp連線

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





相關推薦

Android開發hfp連線

本文主要內容是藍芽手機音訊的連線、斷開流程分析,對應藍芽HFP profile。 該文章是基於Android原始碼4.3的 1 hfp簡單介紹 HFP (Hands-free Profile),讓藍芽裝置(如藍芽耳機)可以控制電話,如接聽、結束通話、拒接、語音撥號等,

Android 開發(八)hfp接聽、結束通話電話

本文已授權微信公眾號 fanfan程式媛 獨家釋出 掃一掃文章底部的二維碼或在微信搜尋 fanfan程式媛 即可關注 繼續研究hfp相關功能。藍芽耳機可以控制手機接聽、拒接、結束通話電話,撥打電話等功能。本文主要分析下起這些操作的大致流程。 在

Android 開發(七)hfp音訊連線

本文已授權微信公眾號 fanfan程式媛 獨家釋出 掃一掃文章底部的二維碼或在微信搜尋 fanfan程式媛 即可關注 接著上一篇hfp連線繼續,檢視藍芽通話時如何進行處理的。hfp連線有兩個連線,一個是hfp連線(在設定介面顯示的是手機音訊),另

Android 開發常用UUID表

        // Sample Services.          attributes.put("0000180d-0

Android開發之低功耗(4.0)開發全記錄

主要內容概況 前面我記錄了開發藍芽2.0的過程,今天準備介紹現在的主流藍芽技術,藍芽4.0的開發,該藍芽技術是在Android4.3(API級別18)版本引入的API。 官方文件 具體的區別主要以下幾點: 1.新的藍芽技術提供了連線服務的方法,以前是沒有提供連線藍芽的方法

Android開發之經典2.0)開發全記錄

前言部分 最近因為需要開始藍芽相關開發,所以在網上搜索了很多內容,並且結合自己的開發過程做了一個總結,先儲備上,也許可能幫到正在做藍芽開發的同學。 藍芽很早就是android裝置上基本通訊功能了,只是以前的沒有那麼多藍芽裝置,現在藍芽裝置種類繁多,所以經常會有人遇到藍芽相關的開發

1 android開發---與模組進行通訊

原文地址 http://www.cnblogs.com/wenjiang/p/3200138.html 近半個月來一直在搞android藍芽這方面,主要是專案需要與藍芽模組進行通訊。開頭的進展很順利,但因為藍芽模組不在我這裡,所以只能用手機測試。一開頭就發現手機的藍芽

Android 開發 —— BLE

藍芽——BLE 介紹 1.BLE 是 Bluetooth Low Energy 的縮寫,意思為低功耗藍芽。由藍芽技術聯盟(Bluetooth SIG)設計的無線通訊技術,主要用於醫療,健身,安全和家庭娛樂行業。 與傳統藍芽相比,藍芽低功耗旨在大幅降低功耗

Android 開發之搜尋、配對、連線、通訊大全

        藍芽( Bluetooth®):是一種無線技術標準,可實現固定裝置、移動裝置和樓宇個人域網之間的短距離資料交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍芽裝置最多可以同時和7個其它藍芽裝置建立連線,進行通訊,當然並不是每一個藍芽都可以達到

android 開發基本流程

原文連結  http://blog.csdn.net/q610098308/article/details/45248423  侵刪 此例子基於 android demo 對於一般的軟體開發人員來說,藍芽是很少用到的,尤其是Android的藍芽開發,國內的例子很

Android開發教程(一)——搜尋裝置

Android藍芽功能的開發一直是很多新手頭疼的問題,網上雖然也有很多教程貼,但大多都寫得不全面,不能讓我們真正掌握藍芽開發的基本知識。本教程主要講解搜尋藍芽裝置、藍芽裝置之間的連線和藍芽之間的通訊三個主要模組。掌握了這三個,基本就能進行簡單的藍芽開發了。

Android開發(一)之開啟和裝置搜尋

Android藍芽開發系列目錄: 一、判斷是否系統是否支援藍芽 在使用藍芽之前,我們首先要判斷手機裝置是否支援藍芽,雖然現在基本都支援藍芽了,但是為了程式碼的嚴謹性我們還是需要在程式碼中判斷: private BluetoothManager bluetoothma

android開發-與模組通訊

因為我馬上要離職了,在很多官網上看到招聘android基本都要會藍芽開發,wifi的經驗,所以就來學習下藍芽! 於是,我得到了很好的教訓:請確保專案中的最關鍵因素是否在我們的掌握中。像是藍芽模組這種東西,應該今早催促對方拿過來才是,而不是自己一個人在那邊瞎搞。 嘮叨話就先到

android開發 裝置的查詢和連線

Android對於藍芽開發從2.0版本的sdk才開始支援,而且模擬器不支援,測試至少需要兩部手機,所以制約了很多技術人員的開發。 1. 首先,要操作藍芽,先要在AndroidManifest.xml里加入許可權 // 管理藍芽裝置的許可權 <

Android 開發連印表機。

public synchronized void connect(String macAddress, BluetoothDevice device) { if (mThread != null) { mThread.interrupt(); m

關於android開發的一些總結

   最近三個月一直在忙著公司的一個產品。這個產品主要是藍芽和微控制器之間的資料交換。當時在選擇通訊方式的時候有2種選擇。一種是wifi,另外一種就是後來選擇的藍芽。期間遇到了很多問題,包括藍芽連線問題啊,android版本問題,bluesocket連線問題等等。    1

android 開發常見問題總結

  1、主機和從機有什麼區別 主機是主動搜尋的,從機是被搜尋的,主機可以記憶從機地址、密碼,也可以放棄這些記憶 重新搜尋。 (HC-06)主機如果配對過從機,那麼就會記憶該從機的地址和密碼,下次上電就

Android開發(二) BLE4.0低功耗

一、BLE4.0低功耗藍芽 Bluetooth Low Energy,藍芽低功耗,是從藍芽4.0開始支援的技術。相較傳統藍芽,傳輸速度更快、覆蓋範圍廣、安全性高、延時短、耗電低等特點。 二、關鍵術語 1.GATT(通用屬性配置):通用屬性配置檔案,用於ble鏈路上傳送和接

Android 開發(九)A2DP基本功能

本文主要是Android做為Audio Source端,A2DP的基本操作:包括連線、斷開連線、設定優先順序、獲取優先順序、獲取A2DP連線狀態、獲取A2DP連線的裝置列表等功能。 1 簡介 A2DP全名是Advanced Audio Distrib

Android 開發(十)A2DP原始碼分析

上一篇說了下A2DP的一些基本操作,這篇分析下系統應用、系統原始碼是如何操作A2DP的。尤其是其連線過程,基於Android4.3原始碼。Andorid手機一般都是做為A2DP Audio Source端。 1 連線過程 媒體音訊也就是A2DP,首先連線的藍芽裝置需要