Android之Bluetooth配對

前言

我們關注藍牙建立連接的過程
1.通信的建立一定是異步的過程殴穴,自然涉及回調
2.如果有回調穿仪,一定有一處代碼進行分發(fā)處理

apk -- jni -- hal 
apk的監(jiān)聽一定來自jni,我們關注jni的注冊

解讀JniCallbacks

/*
 * Copyright (C) 2012-2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth.btservice;

import android.util.Log;

final class JniCallbacks {

    private RemoteDevices mRemoteDevices;
    private AdapterProperties mAdapterProperties;
    private AdapterState mAdapterStateMachine;
    private BondStateMachine mBondStateMachine;

    JniCallbacks(AdapterState adapterStateMachine,AdapterProperties adapterProperties) {
        mAdapterStateMachine = adapterStateMachine;
        mAdapterProperties = adapterProperties;
    }

    void init(BondStateMachine bondStateMachine, RemoteDevices remoteDevices) {
        mRemoteDevices = remoteDevices;
        mBondStateMachine = bondStateMachine;
    }

    void cleanup() {
        mRemoteDevices = null;
        mAdapterProperties = null;
        mAdapterStateMachine = null;
        mBondStateMachine = null;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
            int passkey) {//藍牙芯片通知配對確認
        mBondStateMachine.sspRequestCallback(address, name, cod, pairingVariant,
            passkey);
    }
    void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] val) {//用于記錄藍牙設備的數(shù)據(jù),主要涉及的類為RemoteDevices.DeviceProperties。例如藍牙地址 藍牙uuid
        mRemoteDevices.devicePropertyChangedCallback(address, types, val);
    }

    void deviceFoundCallback(byte[] address) {//掃描設備的回調
        mRemoteDevices.deviceFoundCallback(address);
    }

    void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) {
        mBondStateMachine.pinRequestCallback(address, name, cod, min16Digits);
    }

    void bondStateChangeCallback(int status, byte[] address, int newState) {//配對狀態(tài)更改
        mBondStateMachine.bondStateChangeCallback(status, address, newState);
    }

    void aclStateChangeCallback(int status, byte[] address, int newState) {//配置的設備狀態(tài)更改诉位,例如遠程設備關閉了藍牙,會通知出來菜枷,具體的時間是多久苍糠?
        mRemoteDevices.aclStateChangeCallback(status, address, newState);
    }

    void stateChangeCallback(int status) {//本設備的藍牙打開和關閉通知
        mAdapterStateMachine.stateChangeCallback(status);
    }

    void discoveryStateChangeCallback(int state) {//掃描開始和掃描結束,開始是1 結束是0
        mAdapterProperties.discoveryStateChangeCallback(state);
    }

    void adapterPropertyChangedCallback(int[] types, byte[][] val) {//不是很清楚用來做什么
        mAdapterProperties.adapterPropertyChangedCallback(types, val);
    }

}

場景:配對建立連接過程

設備端:
UI端點擊配對 -- 系統(tǒng)服務 -- Bluetooth.apk -- jni -- hal ---->  接受方


接受方:
接受方 -- 發(fā)送pin指令進行配對 --> 設備端

設備端
設備端收到指令hal -- jni -- Bluetooth.apk -- UI端接受點擊確認
JniCallbacks.sspRequestCallback 接收到回調之后啤誊,進行事件分發(fā)

案例:藍牙配對連接
注:
配對的前提岳瞭,設備已知,代碼分析要從設備信息出發(fā)

1.UI調用方法
BluetoothDevice.createBond

    public boolean createBond() {
        ······
        try {
            return sService.createBond(this, TRANSPORT_AUTO);//sService為Bluetooth.apk中的服務
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return false;
    }
    注:
    sService的獲取來自系統(tǒng)服務BluetoothManagerService

2.藍牙app及jni
1)AdapterService.AdapterServiceBinder.createBond

AdapterService.AdapterServiceBinder.createBond --> AdapterService.createBond

     boolean createBond(BluetoothDevice device, int transport, OobData oobData) {
        ······
        // Pairing is unreliable while scanning, so cancel discovery
        // Note, remove this when native stack improves
        cancelDiscoveryNative();//注意配對的時候需要取消掃描

        Message msg = mBondStateMachine.obtainMessage(BondStateMachine.CREATE_BOND);
        msg.obj = device;
        msg.arg1 = transport;

        if (oobData != null) {
            Bundle oobDataBundle = new Bundle();
            oobDataBundle.putParcelable(BondStateMachine.OOBDATA, oobData);
            msg.setData(oobDataBundle);
        }
        mBondStateMachine.sendMessage(msg);//mBondStateMachine為狀態(tài)機蚊锹。要熟悉狀態(tài)機的運用
        return true;
    }

2)BondStateMachine.StableState接受來自消息CREATE_BOND

mBondStateMachine的默認狀態(tài)為mStableState
    private BondStateMachine(AdapterService service,
            AdapterProperties prop, RemoteDevices remoteDevices) {
        super("BondStateMachine:");
        addState(mStableState);
        addState(mPendingCommandState);
        ······
        setInitialState(mStableState);
    }
    
    private class StableState extends State {
        ······
        @Override
        public boolean processMessage(Message msg) {
            ·······
            switch(msg.what) {
              ·······
              case CREATE_BOND:
                  OobData oobData = null;
                  if (msg.getData() != null)
                      oobData = msg.getData().getParcelable(OOBDATA);

                  createBond(dev, msg.arg1, oobData, true);
                  break;
              ······
            }
            ······
        }
    }
    
    private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
                               boolean transition) {
        if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
            infoLog("Bond address is:" + dev);
            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
            boolean result;
            if (oobData != null) {
                result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData);
            } else {
                result = mAdapterService.createBondNative(addr, transport);//調用jni進行通信
            }

            if (!result) {
                sendIntent(dev, BluetoothDevice.BOND_NONE,
                           BluetoothDevice.UNBOND_REASON_REMOVED);
                return false;
            } else if (transition) {
                transitionTo(mPendingCommandState);//同步把狀態(tài)切換成帶處理狀態(tài)(mPendingCommandState)
            }
            return true;
        }
        return false;
    }

3)AdapterService.createBondNative

/*package*/ native boolean createBondNative(byte[] address, int transport);

com_android_bluetooth_btservice_AdapterService.cpp
static jboolean createBondNative(JNIEnv* env, jobject obj, jbyteArray address, jint transport) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr;
    jboolean result = JNI_FALSE;

    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    int ret = sBluetoothInterface->create_bond((bt_bdaddr_t *)addr, transport);//通過jni調進藍牙協(xié)議棧中
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

這里是沒有對應的回調傳給jni,那自然是jni主動被調用,這樣才能把配對的狀態(tài)告知app

3.藍牙協(xié)議棧--省略

4.Bluetooth.apk接收回調
1)關注JniCallbacks接收回調的地方

    void bondStateChangeCallback(int status, byte[] address, int newState) {//配對狀態(tài)更改
        mBondStateMachine.bondStateChangeCallback(status, address, newState);
    }
    
    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
            int passkey) {//藍牙芯片通知配對確認
        mBondStateMachine.sspRequestCallback(address, name, cod, pairingVariant,
            passkey);
    }

2)BondStateMachine
a)關注配對狀態(tài)的回調

    void bondStateChangeCallback(int status, byte[] address, int newState) {
        ······
        Message msg = obtainMessage(BONDING_STATE_CHANGE);//狀態(tài)機目前處于mPendingCommandState
        msg.obj = device;

        if (newState == BOND_STATE_BONDED)
            msg.arg1 = BluetoothDevice.BOND_BONDED;
        else if (newState == BOND_STATE_BONDING)
            msg.arg1 = BluetoothDevice.BOND_BONDING;
        else
            msg.arg1 = BluetoothDevice.BOND_NONE;
        msg.arg2 = status;

        sendMessage(msg);
    }
    
    private class PendingCommandState extends State {
        ······
        @Override
        public boolean processMessage(Message msg) {
            ······
            switch (msg.what) {
                ······
                case BONDING_STATE_CHANGE:
                    int newState = msg.arg1;
                    int reason = getUnbondReasonFromHALCode(msg.arg2);
                    sendIntent(dev, newState, reason);//發(fā)送廣播出去
                    ······
                    break;
                ······
            }
            ······
        }
    }
    
    private void sendIntent(BluetoothDevice device, int newState, int reason) {
        DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
        int oldState = BluetoothDevice.BOND_NONE;
        if (devProp != null) {
            oldState = devProp.getBondState();
        }
        if (oldState == newState) return;
        mAdapterProperties.onBondStateChanged(device, newState);

        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//關注廣播
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
        if (newState == BluetoothDevice.BOND_NONE)
            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
                AdapterService.BLUETOOTH_PERM);
        infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
                + " NewState: " + newState);
    }
    
    總結:
    針對配對的狀態(tài)瞳筏,其他應用關注廣播:BluetoothDevice.ACTION_BOND_STATE_CHANGED

b)關注配置過程確認pin碼

    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
            int passkey) {
        ······
        Message msg = obtainMessage(SSP_REQUEST);//狀態(tài)機目前處于mPendingCommandState
        msg.obj = device;
        if(displayPasskey)
            msg.arg1 = passkey;
        msg.arg2 = variant;
        sendMessage(msg);
    }
    
    private class PendingCommandState extends State {
        ······
        @Override
        public boolean processMessage(Message msg) {
            ······
            switch (msg.what) {
                ······
                case SSP_REQUEST:
                    int passkey = msg.arg1;
                    int variant = msg.arg2;
                    sendDisplayPinIntent(devProp.getAddress(), passkey, variant);//發(fā)送廣播
                    break;
                ······
            }
            ······
        }
    }
    
    private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
        Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);//關注此廣播
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
        if (pin != 0) {
            intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
        }
        intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
    }
    
    總結:
    針對配對過程獲取pin碼,其他應用關注廣播:BluetoothDevice.ACTION_PAIRING_REQUEST
    

參考學習

http://www.reibang.com/p/a150d55e29ca
https://blog.csdn.net/WHB20081815/article/details/88653177
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末牡昆,一起剝皮案震驚了整個濱河市姚炕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丢烘,老刑警劉巖柱宦,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異播瞳,居然都是意外死亡捷沸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門狐史,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痒给,“玉大人,你說我怎么就攤上這事骏全〔园兀” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵姜贡,是天一觀的道長试吁。 經(jīng)常有香客問我,道長楼咳,這世上最難降的妖魔是什么熄捍? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮母怜,結果婚禮上余耽,老公的妹妹穿的比我還像新娘。我一直安慰自己苹熏,他們只是感情好碟贾,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著轨域,像睡著了一般袱耽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上干发,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天朱巨,我揣著相機與錄音,去河邊找鬼枉长。 笑死冀续,一個胖子當著我的面吹牛,可吹牛的內容都是我干的搀暑。 我是一名探鬼主播沥阳,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼自点!你這毒婦竟也來了桐罕?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤桂敛,失蹤者是張志新(化名)和其女友劉穎功炮,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體术唬,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡薪伏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了粗仓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫁怀。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡设捐,死狀恐怖,靈堂內的尸體忽然破棺而出塘淑,到底是詐尸還是另有隱情萝招,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布存捺,位于F島的核電站槐沼,受9級特大地震影響,放射性物質發(fā)生泄漏捌治。R本人自食惡果不足惜岗钩,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肖油。 院中可真熱鬧兼吓,春花似錦、人聲如沸构韵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疲恢。三九已至凶朗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間显拳,已是汗流浹背棚愤。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杂数,地道東北人宛畦。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像揍移,于是被迫代替她去往敵國和親次和。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容